Refactor SmaliTestBase to go through the normal R8 entry points.

Bug:
Change-Id: Id3e8114ffc2a562790a7043ade5de05be187f449
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index a9f3329..18096f4 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,7 +62,6 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
 public class R8 {
 
@@ -106,51 +105,6 @@
     }
   }
 
-  static DexApplication optimize(
-      DexApplication application,
-      AppInfoWithSubtyping appInfo,
-      InternalOptions options)
-      throws ApiLevelException, ExecutionException, IOException {
-    return new R8(options).optimize(application, appInfo);
-  }
-
-  private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo)
-      throws IOException, ApiLevelException, ExecutionException {
-    return optimize(application, appInfo, GraphLense.getIdentityLense(),
-        Executors.newSingleThreadExecutor());
-  }
-
-  private DexApplication optimize(
-      DexApplication application,
-      AppInfoWithSubtyping appInfo,
-      GraphLense graphLense,
-      ExecutorService executorService)
-      throws IOException, ApiLevelException, ExecutionException {
-    final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
-
-    timing.begin("Create IR");
-    try {
-      IRConverter converter = new IRConverter(
-          appInfo, options, timing, printer, graphLense);
-      application = converter.optimize(application, executorService);
-    } finally {
-      timing.end();
-    }
-
-    if (options.printCfg) {
-      if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
-        System.out.print(printer.toString());
-      } else {
-        try (OutputStreamWriter writer = new OutputStreamWriter(
-            new FileOutputStream(options.printCfgFile),
-            StandardCharsets.UTF_8)) {
-          writer.write(printer.toString());
-        }
-      }
-    }
-    return application;
-  }
-
   private Set<DexType> filterMissingClasses(Set<DexType> missingClasses,
       ProguardClassFilter dontWarnPatterns) {
     Set<DexType> result = new HashSet<>(missingClasses);
@@ -278,7 +232,26 @@
 
       graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withSubtyping()).run();
 
-      application = optimize(application, appInfo, graphLense, executorService);
+      timing.begin("Create IR");
+      CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
+      try {
+        IRConverter converter = new IRConverter(appInfo, options, timing, printer, graphLense);
+        application = converter.optimize(application, executorService);
+      } finally {
+        timing.end();
+      }
+
+      if (options.printCfg) {
+        if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
+          System.out.print(printer.toString());
+        } else {
+          try (OutputStreamWriter writer = new OutputStreamWriter(
+              new FileOutputStream(options.printCfgFile),
+              StandardCharsets.UTF_8)) {
+            writer.write(printer.toString());
+          }
+        }
+      }
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 44ac9c8..7f7ec36 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -1,11 +1,15 @@
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
 
 public class SmaliWriter extends DexByteCodeWriter {
 
@@ -15,12 +19,15 @@
   }
 
   /** Return smali source for the application code. */
-  public static String smali(DexApplication application, InternalOptions options) {
-    SmaliWriter writer = new SmaliWriter(application, options);
+  public static String smali(AndroidApp application, InternalOptions options) {
     ByteArrayOutputStream os = new ByteArrayOutputStream();
     try (PrintStream ps = new PrintStream(os)) {
+      DexApplication dexApplication = new ApplicationReader(application, options,
+          new Timing("SmaliWriter"))
+          .read();
+      SmaliWriter writer = new SmaliWriter(dexApplication, options);
       writer.write(ps);
-    } catch (IOException e) {
+    } catch (IOException | ExecutionException e) {
       throw new CompilationError("Failed to generate smali sting", e);
     }
     return new String(os.toByteArray(), StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 00787ca..a9735c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -417,7 +418,7 @@
     DexType result;
     int count = 0;
     do {
-      String name = options.outline.className + (count == 0 ? "" : Integer.toString(count));
+      String name = OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
       count++;
       result = appInfo.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
     } while (appInfo.definitionFor(result) != null);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 67e58cb..9709cf0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.collect.Sets;
@@ -1062,7 +1063,7 @@
       MethodAccessFlags methodAccess =
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_PUBLIC | Constants.ACC_STATIC, false);
-      DexString methodName = dexItemFactory.createString(options.outline.methodPrefix + count);
+      DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
       DexMethod method = outline.buildMethod(type, methodName);
       direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
           DexAnnotationSetRefList.empty(), new OutlineCode(outline));
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index c494c08..36ea1b3 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -17,6 +17,7 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
 import org.objectweb.asm.Type;
@@ -291,6 +292,12 @@
       this.parameters = parameters;
     }
 
+    public MethodSignature(String name, String type, Collection<String> parameters) {
+      super(name);
+      this.type = type;
+      this.parameters = parameters.toArray(new String[parameters.size()]);
+    }
+
     public static MethodSignature fromDexMethod(DexMethod method) {
       String[] paramNames = new String[method.getArity()];
       DexType[] values = method.proto.parameters.values;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 67aa288..ddac51f 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -608,7 +608,7 @@
      * Set dead-code data.
      */
     public Builder setDeadCode(byte[] content) {
-      deadCode = content == null ? null : Resource.fromBytes(null, content);
+      deadCode = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
@@ -631,7 +631,7 @@
      * Set proguard-map data.
      */
     public Builder setProguardMapData(byte[] content) {
-      proguardMap = content == null ? null : Resource.fromBytes(null, content);
+      proguardMap = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
@@ -639,7 +639,7 @@
      * Set proguard-seeds data.
      */
     public Builder setProguardSeedsData(byte[] content) {
-      proguardSeeds = content == null ? null : Resource.fromBytes(null, content);
+      proguardSeeds = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
@@ -685,7 +685,7 @@
      * Set the main-dex list output data.
      */
     public Builder setMainDexListOutputData(byte[] content) {
-      mainDexListOutput = content == null ? null : Resource.fromBytes(null, content);
+      mainDexListOutput = content == null ? null : Resource.fromBytes(Origin.unknown(), content);
       return this;
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d7006af..62dac5f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -267,9 +267,10 @@
 
   public static class OutlineOptions {
 
+    public static final String CLASS_NAME = "r8.GeneratedOutlineSupport";
+    public static final String METHOD_PREFIX = "outline";
+
     public boolean enabled = true;
-    public static final String className = "r8.GeneratedOutlineSupport";
-    public String methodPrefix = "outline";
     public int minSize = 3;
     public int maxSize = 99;
     public int threshold = 20;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2128663..60bcc5b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -5,18 +5,20 @@
 package com.android.tools.r8;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.io.ByteStreams;
@@ -368,8 +370,34 @@
    */
   protected void disassemble(AndroidApp app) throws Exception {
     InternalOptions options = new InternalOptions();
-    DexApplication dexApplication = new ApplicationReader(app, options, new Timing("XX")).read();
-    System.out.println(SmaliWriter.smali(dexApplication, options));
+    System.out.println(SmaliWriter.smali(app, options));
+  }
+
+  protected DexEncodedMethod getMethod(
+      DexInspector inspector,
+      String className,
+      String returnType,
+      String methodName,
+      List<String> parameters) {
+    ClassSubject clazz = inspector.clazz(className);
+    assertTrue(clazz.isPresent());
+    MethodSubject method = clazz.method(returnType, methodName, parameters);
+    assertTrue(method.isPresent());
+    return method.getMethod();
+  }
+
+  protected DexEncodedMethod getMethod(
+      AndroidApp application,
+      String className,
+      String returnType,
+      String methodName,
+      List<String> parameters) {
+    try {
+      DexInspector inspector = new DexInspector(application);
+      return getMethod(inspector, className, returnType, methodName, parameters);
+    } catch (Exception e) {
+      return null;
+    }
   }
 
   public enum MinifyMode {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2c4bc98..34230de 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.shaking.FilteredClassPath;
@@ -697,14 +696,6 @@
     return compatSink.build();
   }
 
-  public static DexApplication optimizeWithR8(
-      DexApplication application,
-      InternalOptions options)
-      throws CompilationException, ExecutionException, IOException {
-    application = application.toDirect();
-    return R8.optimize(application, new AppInfoWithSubtyping(application), options);
-  }
-
   public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException {
     return runD8(app, null);
   }
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index ba49e64..fc6e4c1 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.smali.SmaliTestBase.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
index 66652bd..7a911be 100644
--- a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
@@ -12,15 +12,13 @@
 import com.android.tools.r8.debuginfo.DebugInfoInspector;
 import com.android.tools.r8.graph.DexDebugEntry;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApp;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
-import org.junit.Assume;
-import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index b344c8c..8d93b31 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,9 +10,10 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -62,10 +63,11 @@
         "    goto :return"
     );
 
-    DexApplication application = buildApplication(builder);
-    AppInfo appInfo = new AppInfo(application);
-    DexEncodedMethod method =
-        getMethod(application, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of());
+    AndroidApp application = buildApplication(builder);
+    AppInfo appInfo = getAppInfo(application);
+    DexInspector inspector = new DexInspector(appInfo.app);
+    DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
+        ImmutableList.of());
     assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
     assertNull(appInfo.lookupDirectTarget(method.method));
     assertNotNull(appInfo.lookupStaticTarget(method.method));
@@ -74,12 +76,12 @@
       // Dalvik rejects at verification time instead of producing the
       // expected IncompatibleClassChangeError.
       try {
-        runArt(application, new InternalOptions());
+        runArt(application);
       } catch (AssertionError e) {
         assert e.toString().contains("VerifyError");
       }
     } else {
-      assertEquals("OK", runArt(application, new InternalOptions()));
+      assertEquals("OK", runArt(application));
     }
   }
 
@@ -131,20 +133,21 @@
         "    goto :return"
     );
 
-    DexApplication application = buildApplication(builder);
-    AppInfo appInfo = new AppInfo(application);
+    AndroidApp application = buildApplication(builder);
+    AppInfo appInfo = getAppInfo(application);
+    DexInspector inspector = new DexInspector(appInfo.app);
 
     DexMethod methodXOnTestSuper =
-        getMethod(application, "TestSuper", "int", "x", ImmutableList.of()).method;
+        getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
     DexMethod methodYOnTest =
-        getMethod(application, "Test", "int", "y", ImmutableList.of()).method;
+        getMethod(inspector, "Test", "int", "y", ImmutableList.of()).method;
 
     DexType classTestSuper = methodXOnTestSuper.getHolder();
     DexType classTest = methodYOnTest.getHolder();
     DexProto methodXProto = methodXOnTestSuper.proto;
     DexString methodXName = methodXOnTestSuper.name;
     DexMethod methodXOnTest =
-        application.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
+        appInfo.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
 
     assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper));
     assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper));
@@ -156,7 +159,7 @@
     assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper));
     assertNotNull(appInfo.lookupStaticTarget(methodXOnTest));
 
-    assertEquals("OK", runArt(application, new InternalOptions()));
+    assertEquals("OK", runArt(application));
   }
 }
 
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
index ad9a26a..2d73d1f 100644
--- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,13 +4,15 @@
 
 package com.android.tools.r8.ir;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -45,8 +47,7 @@
         "    return              p0"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication application = buildApplication(builder, options);
+    AndroidApp application = buildApplication(builder);
 
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 27a3ce9..b190933 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -13,7 +13,8 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -22,7 +23,7 @@
 import java.util.ListIterator;
 import org.junit.Test;
 
-public class InlineTest extends SmaliTestBase {
+public class InlineTest extends IrInjectionTestBase {
 
   TestApplication codeForMethodReplaceTest(int a, int b) throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
@@ -84,7 +85,7 @@
     DexEncodedMethod methodB = getMethod(application, signatureB);
     IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator);
 
-    return new SmaliTestBase.TestApplication(application, method, code,
+    return new TestApplication(application, method, code,
         ImmutableList.of(codeA, codeB), valueNumberGenerator, options);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
index a688ffe..5d3b0fb 100644
--- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -4,14 +4,16 @@
 
 package com.android.tools.r8.ir;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
@@ -46,8 +48,7 @@
         "    return              p0"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication application = buildApplication(builder, options);
+    AndroidApp application = buildApplication(builder);
 
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
new file mode 100644
index 0000000..9f3e0ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir;
+
+import com.android.tools.r8.R8;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppOutputSink;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import org.antlr.runtime.RecognitionException;
+
+public class IrInjectionTestBase extends SmaliTestBase {
+
+  protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+    try {
+      return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options);
+    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
+    try {
+      options.itemFactory.resetSortedIndices();
+      return new ApplicationReader(input, options, new Timing("IrInjectionTest")).read();
+    } catch (IOException | ExecutionException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
+    return getMethod(application,
+        signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
+  }
+
+  protected DexEncodedMethod getMethod(
+      DexApplication application,
+      String className,
+      String returnType,
+      String methodName,
+      List<String> parameters) {
+    DexInspector inspector = new DexInspector(application);
+    return getMethod(inspector, className, returnType, methodName, parameters);
+  }
+
+  public class TestApplication {
+
+    public final DexApplication application;
+    public final DexEncodedMethod method;
+    public final IRCode code;
+    public final List<IRCode> additionalCode;
+    public final ValueNumberGenerator valueNumberGenerator;
+    public final InternalOptions options;
+
+    public TestApplication(
+        DexApplication application,
+        DexEncodedMethod method,
+        IRCode code,
+        ValueNumberGenerator valueNumberGenerator,
+        InternalOptions options) {
+      this(application, method, code, null, valueNumberGenerator, options);
+    }
+
+    public TestApplication(
+        DexApplication application,
+        DexEncodedMethod method,
+        IRCode code,
+        List<IRCode> additionalCode,
+        ValueNumberGenerator valueNumberGenerator,
+        InternalOptions options) {
+      this.application = application;
+      this.method = method;
+      this.code = code;
+      this.additionalCode = additionalCode;
+      this.valueNumberGenerator = valueNumberGenerator;
+      this.options = options;
+    }
+
+    public int countArgumentInstructions() {
+      int count = 0;
+      ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
+      while (iterator.next().isArgument()) {
+        count++;
+      }
+      return count;
+    }
+
+    public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
+      InstructionListIterator iterator = block.listIterator();
+      for (int i = 0; i < index; i++) {
+        iterator.next();
+      }
+      return iterator;
+    }
+
+    private AndroidApp writeDex(DexApplication application, InternalOptions options)
+        throws DexOverflowException {
+      try {
+        AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
+        R8.writeApplication(
+            Executors.newSingleThreadExecutor(),
+            application,
+            compatSink,
+            null,
+            NamingLens.getIdentityLens(),
+            null,
+            options);
+        compatSink.close();
+        return compatSink.build();
+      } catch (ExecutionException | IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    public String run() throws DexOverflowException, IOException {
+      AppInfo appInfo = new AppInfo(application);
+      IRConverter converter = new IRConverter(appInfo, options);
+      converter.replaceCodeForTesting(method, code);
+      AndroidApp app = writeDex(application, options);
+      return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index 5165432..25a237d 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -21,14 +21,15 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
 
-public class SplitBlockTest extends SmaliTestBase {
+public class SplitBlockTest extends IrInjectionTestBase {
 
   TestApplication codeWithoutCatchHandlers() throws Exception {
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index 1d17e31..74a91a8 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -3,13 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.PriorityQueue;
@@ -43,7 +45,7 @@
         ImmutableList.of(),
         1,
         "    return-void");
-    DexApplication application = buildApplication(builder, options);
+    AndroidApp application = buildApplication(builder);
     // Build the code, and split the code into three blocks.
     ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
     DexEncodedMethod method = getMethod(application, signature);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 7421046..5579380 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -4,18 +4,15 @@
 package com.android.tools.r8.jasmin;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.Resource;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.StringUtils;
@@ -33,13 +30,8 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
 
-public class JasminTestBase {
-
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+public class JasminTestBase extends TestBase {
 
   public static String getPathFromDescriptor(String classDescriptor) {
     assert classDescriptor.startsWith("L");
@@ -95,6 +87,10 @@
     return runOnArt(compileWithD8(builder), main);
   }
 
+  protected AndroidApp compileWithR8(JasminBuilder builder) throws Exception {
+    return compileWithR8(builder, null);
+  }
+
   protected AndroidApp compileWithR8(JasminBuilder builder,
       Consumer<InternalOptions> optionsConsumer)
       throws Exception {
@@ -169,20 +165,6 @@
   protected DexEncodedMethod getMethod(AndroidApp application, String clazz,
       MethodSignature signature) {
     return getMethod(application,
-        clazz, signature.type, signature.name, signature.parameters);
-  }
-
-  protected DexEncodedMethod getMethod(AndroidApp application, String className,
-      String returnType, String methodName, String[] parameters) {
-    try {
-      DexInspector inspector = new DexInspector(application);
-      ClassSubject clazz = inspector.clazz(className);
-      assertTrue(clazz.isPresent());
-      MethodSubject method = clazz.method(returnType, methodName, Arrays.asList(parameters));
-      assertTrue(method.isPresent());
-      return method.getMethod();
-    } catch (Exception e) {
-      return null;
-    }
+        clazz, signature.type, signature.name, Arrays.asList(signature.parameters));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
index acb0a51..de654b8 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 4d1d619..51772fe 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.Sput;
 import com.android.tools.r8.code.SputObject;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -23,11 +22,11 @@
 import com.android.tools.r8.graph.DexValue.DexValueLong;
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 
@@ -92,9 +91,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
@@ -147,7 +145,7 @@
     assertTrue(value instanceof DexValueString);
     assertEquals(("8"), ((DexValueString) value).getValue().toString());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("true", "1", "2", "3", "4", "5.0", "6.0", "7", "8"), result);
   }
@@ -177,16 +175,15 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     MethodSubject clinit = inspector.clazz("Test").clinit();
     // Nothing changed in the class initializer.
     assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("0", "1"), result);
   }
@@ -219,16 +216,15 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("null", "null", "null"), result);
   }
@@ -262,16 +258,15 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("Value1", "Value2", "Value2"), result);
   }
@@ -313,9 +308,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
@@ -333,7 +327,7 @@
     assertTrue(value instanceof DexValueString);
     assertEquals(("7"), ((DexValueString) value).getValue().toString());
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("3", "7") , result);
   }
@@ -387,9 +381,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz("Test").clinit().isPresent());
@@ -417,7 +410,7 @@
       }
     }
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("3", "7"), result);
   }
@@ -475,9 +468,8 @@
         "return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
@@ -485,7 +477,7 @@
     assertTrue(
         inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
 
-    String result = runArt(processedApplication, options, className);
+    String result = runArt(processedApplication, className);
 
     assertEquals(StringUtils.lines("Test", className, "Test", className, "Test", className),
         result);
@@ -525,15 +517,14 @@
 
     builder.addClass("org.example.Test2");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
     assertTrue(inspector.clazz(className).clinit().isPresent());
 
-    String result = runArt(processedApplication, options, className);
+    String result = runArt(processedApplication, className);
 
     assertEquals(StringUtils.lines("Test2", "org.example.Test2"), result);
   }
@@ -559,16 +550,15 @@
     builder.addClass("Other");
     builder.addStaticField("field", "I", "1");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexInspector inspector = new DexInspector(processedApplication);
     MethodSubject clinit = inspector.clazz("Test").clinit();
     // Nothing changed in the class initializer.
     assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
 
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
 
     assertEquals(StringUtils.lines("2"), result);
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index d1a2514..76c24f9 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.PreloadedClassFileProvider;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
rename to src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
index f47025a..93b7c19 100644
--- a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.smali;
+package com.android.tools.r8.rewrite.switches;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
new file mode 100644
index 0000000..66cf986
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -0,0 +1,344 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+
+public class SwitchRewritingJarTest extends JasminTestBase {
+
+  private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    String switchCode;
+    if (packed) {
+      switchCode = StringUtils.join(
+          "\n",
+          "    tableswitch " + key,
+          "      case_0",
+          "      default : case_default");
+    } else {
+      switchCode = StringUtils.join(
+          "\n",
+          "    lookupswitch",
+          "      " + key + " : case_0",
+          "      default : case_default");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload 0",
+        switchCode,
+        "  case_0:",
+        "    iconst_3",
+        "    goto return_",
+        "  case_default:",
+        "    ldc 5",
+        "  return_:",
+        "    ireturn");
+
+    clazz.addMainMethod(
+        "    .limit stack 2",
+        "    .limit locals 1",
+        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "    ldc 2",
+        "    invokestatic Test/test(I)I",
+        "    invokevirtual java/io/PrintStream/print(I)V",
+        "    return");
+
+    AndroidApp app = builder.build();
+    app = ToolHelper.runR8(app);
+
+    MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+    DexEncodedMethod method = getMethod(app, "Test", signature);
+    DexCode code = method.getCode().asDexCode();
+    if (key == 0) {
+      assertEquals(5, code.instructions.length);
+      assertTrue(code.instructions[0] instanceof IfEqz);
+    } else {
+      assertEquals(6, code.instructions.length);
+      assertTrue(code.instructions[1] instanceof IfEq);
+    }
+  }
+
+  @Test
+  public void singleCaseJar() throws Exception {
+    for (boolean packed : new boolean[]{true, false}) {
+      runSingleCaseJarTest(packed, Integer.MIN_VALUE);
+      runSingleCaseJarTest(packed, -1);
+      runSingleCaseJarTest(packed, 0);
+      runSingleCaseJarTest(packed, 1);
+      runSingleCaseJarTest(packed, Integer.MAX_VALUE);
+    }
+  }
+
+  private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload 0",
+        "    lookupswitch",
+        "      " + key1 + " : case_1",
+        "      " + key2 + " : case_2",
+        "      default : case_default",
+        "  case_1:",
+        "    iconst_3",
+        "    goto return_",
+        "  case_2:",
+        "    iconst_4",
+        "    goto return_",
+        "  case_default:",
+        "    iconst_5",
+        "  return_:",
+        "    ireturn");
+
+    clazz.addMainMethod(
+        "    .limit stack 2",
+        "    .limit locals 1",
+        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "    ldc 2",
+        "    invokestatic Test/test(I)I",
+        "    invokevirtual java/io/PrintStream/print(I)V",
+        "    return");
+
+    AndroidApp app = compileWithR8(builder);
+
+    MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+    DexEncodedMethod method = getMethod(app, "Test", signature);
+    DexCode code = method.getCode().asDexCode();
+    if (SwitchRewritingTest.twoCaseWillUsePackedSwitch(key1, key2)) {
+      assertTrue(code.instructions[0] instanceof PackedSwitch);
+    } else {
+      if (key1 == 0) {
+        assertTrue(code.instructions[0] instanceof IfEqz);
+      } else {
+        // Const instruction before if.
+        assertTrue(code.instructions[1] instanceof IfEq);
+      }
+    }
+  }
+
+  @Test
+  public void twoCaseSparseToPackedJar() throws Exception {
+    for (int delta = 1; delta <= 3; delta++) {
+      runTwoCaseSparseToPackedJarTest(0, delta);
+      runTwoCaseSparseToPackedJarTest(-delta, 0);
+      runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+      runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+    }
+    runTwoCaseSparseToPackedJarTest(-1, 1);
+    runTwoCaseSparseToPackedJarTest(-2, 1);
+    runTwoCaseSparseToPackedJarTest(-1, 2);
+    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+  }
+
+  private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
+      Integer additionalLastKey) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    StringBuilder switchSource = new StringBuilder();
+    StringBuilder targetCode = new StringBuilder();
+    for (int i = 0; i < totalKeys; i++) {
+      String caseLabel = "case_" + i;
+      switchSource.append("      " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
+      targetCode.append("  " + caseLabel + ":\n");
+      targetCode.append("    ldc " + i + "\n");
+      targetCode.append("    goto return_\n");
+    }
+    if (additionalLastKey != null) {
+      String caseLabel = "case_" + totalKeys;
+      switchSource.append("      " + additionalLastKey + " : " + caseLabel + "\n");
+      targetCode.append("  " + caseLabel + ":\n");
+      targetCode.append("    ldc " + totalKeys + "\n");
+      targetCode.append("    goto return_\n");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload 0",
+        "  lookupswitch",
+        switchSource.toString(),
+        "      default : case_default",
+        targetCode.toString(),
+        "  case_default:",
+        "    iconst_5",
+        "  return_:",
+        "    ireturn");
+
+    clazz.addMainMethod(
+        "    .limit stack 2",
+        "    .limit locals 1",
+        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "    ldc 2",
+        "    invokestatic Test/test(I)I",
+        "    invokevirtual java/io/PrintStream/print(I)V",
+        "    return");
+
+    AndroidApp app = compileWithR8(builder);
+
+    MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int"));
+    DexEncodedMethod method = getMethod(app, "Test", signature);
+    DexCode code = method.getCode().asDexCode();
+    int packedSwitchCount = 0;
+    int sparseSwitchCount = 0;
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof PackedSwitch) {
+        packedSwitchCount++;
+      }
+      if (instruction instanceof SparseSwitch) {
+        sparseSwitchCount++;
+      }
+    }
+    if (keyStep <= 2) {
+      assertEquals(1, packedSwitchCount);
+      assertEquals(0, sparseSwitchCount);
+    } else {
+      assertEquals(0, packedSwitchCount);
+      assertEquals(1, sparseSwitchCount);
+    }
+  }
+
+  @Test
+  public void largerSwitchJar() throws Exception {
+    runLargerSwitchJarTest(0, 1, 100, null);
+    runLargerSwitchJarTest(0, 2, 100, null);
+    runLargerSwitchJarTest(0, 3, 100, null);
+    runLargerSwitchJarTest(100, 100, 100, null);
+    runLargerSwitchJarTest(-10000, 100, 100, null);
+    runLargerSwitchJarTest(-10000, 200, 100, 10000);
+    runLargerSwitchJarTest(
+        Integer.MIN_VALUE, (int) ((-(long) Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+    // This is the maximal value possible with Jasmin with the generated code above. It depends on
+    // the source, so making smaller source can raise this limit. However we never get close to the
+    // class file max.
+    runLargerSwitchJarTest(0, 1, 5503, null);
+  }
+
+  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
+      int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    StringBuilder x = new StringBuilder();
+    StringBuilder y = new StringBuilder();
+    for (Integer key : keys) {
+      x.append(key).append(" : case_").append(key).append("\n");
+      y.append("case_").append(key).append(":\n");
+      y.append("    ldc ").append(key).append("\n");
+      y.append("    goto return_\n");
+    }
+
+    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
+        "    .limit stack 1",
+        "    .limit locals 1",
+        "    iload_0",
+        "    lookupswitch",
+        x.toString(),
+        "      default : case_default",
+        y.toString(),
+        "  case_default:",
+        "    ldc " + defaultValue,
+        "  return_:",
+        "    ireturn");
+
+    // Add the Jasmin class and a class from Java source with the main method.
+    AndroidApp.Builder appBuilder = AndroidApp.builder();
+    appBuilder.addClassProgramData(builder.buildClasses());
+    appBuilder.addProgramFiles(FilteredClassPath
+        .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
+    AndroidApp app = compileWithR8(appBuilder.build());
+
+    DexInspector inspector = new DexInspector(app);
+    MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
+    DexCode code = method.getMethod().getCode().asDexCode();
+
+    int packedSwitches = 0;
+    int sparseSwitches = 0;
+    int ifs = 0;
+    for (Instruction instruction : code.instructions) {
+      if (instruction instanceof PackedSwitch) {
+        packedSwitches++;
+      }
+      if (instruction instanceof SparseSwitch) {
+        sparseSwitches++;
+      }
+      if (instruction instanceof IfEq || instruction instanceof IfEqz) {
+        ifs++;
+      }
+    }
+
+    assertEquals(expectedPackedSwitches, packedSwitches);
+    assertEquals(expectedSparceSwitches, sparseSwitches);
+    assertEquals(expectedIfs, ifs);
+
+    // Run the code
+    List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
+    args.add(Integer.toString(defaultValue));
+    runOnArt(app, CheckSwitchInTestClass.class, args);
+  }
+
+  @Test
+  public void convertCasesToIf() throws Exception {
+    // Switches that are completely converted to ifs.
+    runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
+
+    // Switches that are completely converted to ifs and one switch.
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
+
+    // Switches that are completely converted to ifs and two switches.
+    runConvertCasesToIf(ImmutableList.of(
+        0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
+
+    // Switches that are completely converted two switches (one sparse and one packed).
+    runConvertCasesToIf(ImmutableList.of(
+        -1000, -900, -800, -700, -600, -500, -400, -300,
+        1000, 1001, 1002, 1003, 1004,
+        2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
new file mode 100644
index 0000000..3d921b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -0,0 +1,250 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.switches;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.Const;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstHigh16;
+import com.android.tools.r8.code.IfEq;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.PackedSwitch;
+import com.android.tools.r8.code.SparseSwitch;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class SwitchRewritingTest extends SmaliTestBase {
+
+  static boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
+    assert key1 != key2;
+    return Math.abs((long) key1 - (long) key2) == 1;
+  }
+
+  private boolean some16BitConst(Instruction instruction) {
+    return instruction instanceof Const4
+        || instruction instanceof ConstHigh16
+        || instruction instanceof Const;
+  }
+  private void runSingleCaseDexTest(boolean packed, int key) {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+    String switchInstruction;
+    String switchData;
+    if (packed) {
+      switchInstruction = "packed-switch";
+      switchData = StringUtils.join(
+          "\n",
+          "  :switch_data",
+          "  .packed-switch " + key,
+          "    :case_0",
+          "  .end packed-switch");
+    } else {
+      switchInstruction = "sparse-switch";
+      switchData = StringUtils.join(
+          "\n",
+          "  :switch_data",
+          "  .sparse-switch",
+          "    " + key + " -> :case_0",
+          "  .end sparse-switch");
+    }
+    MethodSignature signature = builder.addStaticMethod(
+        "int",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of("int"),
+        0,
+        "    " + switchInstruction + " p0, :switch_data",
+        "    const/4 p0, 0x5",
+        "    goto :return",
+        "  :case_0",
+        "    const/4 p0, 0x3",
+        "  :return",
+        "    return p0",
+        switchData);
+
+    builder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const/4             v1, 0",
+        "    invoke-static       { v1 }, LTest;->method(I)I",
+        "    move-result         v1",
+        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
+        "    return-void"
+    );
+
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    DexEncodedMethod method = getMethod(processedApplication, signature);
+    DexCode code = method.getCode().asDexCode();
+
+    if (key == 0) {
+      assertEquals(5, code.instructions.length);
+      assertTrue(code.instructions[0] instanceof IfEqz);
+    } else {
+      assertEquals(6, code.instructions.length);
+      assertTrue(some16BitConst(code.instructions[0]));
+      assertTrue(code.instructions[1] instanceof IfEq);
+    }
+  }
+
+  @Test
+  public void singleCaseDex() {
+    for (boolean packed : new boolean[]{true, false}) {
+      runSingleCaseDexTest(packed, Integer.MIN_VALUE);
+      runSingleCaseDexTest(packed, -1);
+      runSingleCaseDexTest(packed, 0);
+      runSingleCaseDexTest(packed, 1);
+      runSingleCaseDexTest(packed, Integer.MAX_VALUE);
+    }
+  }
+
+  private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    MethodSignature signature = builder.addStaticMethod(
+        "int",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of("int"),
+        0,
+        "    sparse-switch p0, :sparse_switch_data",
+        "    const/4 v0, 0x5",
+        "    goto :return",
+        "  :case_1",
+        "    const/4 v0, 0x3",
+        "    goto :return",
+        "  :case_2",
+        "    const/4 v0, 0x4",
+        "  :return",
+        "    return v0",
+        "  :sparse_switch_data",
+        "  .sparse-switch",
+        "    " + key1 + " -> :case_1",
+        "    " + key2 + " -> :case_2",
+        "  .end sparse-switch");
+
+    builder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const/4             v1, 0",
+        "    invoke-static       { v1 }, LTest;->method(I)I",
+        "    move-result         v1",
+        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
+        "    return-void"
+    );
+
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    DexEncodedMethod method = getMethod(processedApplication, signature);
+    DexCode code = method.getCode().asDexCode();
+    if (twoCaseWillUsePackedSwitch(key1, key2)) {
+      assertTrue(code.instructions[0] instanceof PackedSwitch);
+    } else {
+      if (key1 == 0) {
+        assertTrue(code.instructions[0] instanceof IfEqz);
+      } else {
+        // Const instruction before if.
+        assertTrue(code.instructions[1] instanceof IfEq);
+      }
+    }
+  }
+
+  @Test
+  public void twoCaseSparseToPackedOrIfsDex() {
+    for (int delta = 1; delta <= 3; delta++) {
+      runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
+      runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
+      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
+      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
+    }
+    runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
+    runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
+    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
+  }
+
+  private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
+      Integer additionalLastKey) throws Exception {
+    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+    StringBuilder switchSource = new StringBuilder();
+    StringBuilder targetCode = new StringBuilder();
+    for (int i = 0; i < totalKeys; i++) {
+      String caseLabel = "case_" + i;
+      switchSource.append("    " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
+      targetCode.append("  :" + caseLabel + "\n");
+      targetCode.append("    goto :return\n");
+    }
+    if (additionalLastKey != null) {
+      String caseLabel = "case_" + totalKeys;
+      switchSource.append("    " + additionalLastKey + " -> :" + caseLabel + "\n");
+      targetCode.append("  :" + caseLabel + "\n");
+      targetCode.append("    goto :return\n");
+    }
+
+    MethodSignature signature = builder.addStaticMethod(
+        "void",
+        DEFAULT_METHOD_NAME,
+        ImmutableList.of("int"),
+        0,
+        "    sparse-switch p0, :sparse_switch_data",
+        "    goto :return",
+        targetCode.toString(),
+        "  :return",
+        "    return-void",
+        "  :sparse_switch_data",
+        "  .sparse-switch",
+        switchSource.toString(),
+        "  .end sparse-switch");
+
+    builder.addMainMethod(
+        2,
+        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+        "    const/4             v1, 0",
+        "    invoke-static       { v1 }, LTest;->method(I)V",
+        "    return-void"
+    );
+
+    Consumer<InternalOptions> optionsConsumer = options -> {
+      options.verbose = true;
+      options.printTimes = true;
+    };
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
+    DexEncodedMethod method = getMethod(processedApplication, signature);
+    DexCode code = method.getCode().asDexCode();
+    if (keyStep <= 2) {
+      assertTrue(code.instructions[0] instanceof PackedSwitch);
+    } else {
+      assertTrue(code.instructions[0] instanceof SparseSwitch);
+    }
+  }
+
+  @Test
+  public void twoMonsterSparseToPackedDex() throws Exception {
+    runLargerSwitchDexTest(0, 1, 100, null);
+    runLargerSwitchDexTest(0, 2, 100, null);
+    runLargerSwitchDexTest(0, 3, 100, null);
+    runLargerSwitchDexTest(100, 100, 100, null);
+    runLargerSwitchDexTest(-10000, 100, 100, null);
+    runLargerSwitchDexTest(-10000, 200, 100, 10000);
+    runLargerSwitchDexTest(
+        Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
+
+    // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
+    // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 909f574..d2391ff 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -6,13 +6,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
 import java.util.Collections;
@@ -62,13 +63,11 @@
         "  invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V",
         "  return-void");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-
+    AndroidApp originalApplication = buildApplication(builder);
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code = method.buildIR(options);
+    IRCode code = method.buildIR(new InternalOptions());
 
     // Find the exit block and assert that the value is a phi merging the exceptional edge
     // with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
index 64aa946..de8b832 100644
--- a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.smali;
 
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import java.util.Arrays;
 import java.util.Collections;
 import org.junit.Test;
@@ -48,9 +48,8 @@
         "  goto :in_try"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
 
     DexEncodedMethod method = getMethod(processedApplication, methodSig);
     assert method.getCode().asDexCode().tries.length > 0;
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 8d4b841..0395177 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -6,9 +6,8 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
@@ -56,10 +55,9 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(smaliBuilder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    String result = runArt(processedApplication, options);
+    AndroidApp originalApplication = buildApplication(smaliBuilder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    String result = runArt(processedApplication);
 
     assertEquals(expectedBuilder.toString(), result);
   }
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 1aa9be7..d7f16e8 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -21,26 +21,26 @@
 import com.android.tools.r8.code.ReturnObject;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.ReturnWide;
-import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import org.antlr.runtime.RecognitionException;
 import org.junit.Test;
 
 public class OutlineTest extends SmaliTestBase {
@@ -52,6 +52,15 @@
     return result;
   }
 
+  private Consumer<InternalOptions> configureOptions(Consumer<OutlineOptions> optionsConsumer) {
+    return options -> {
+      // Disable inlining to make sure that code looks as expected.
+      options.inlineAccessors = false;
+      // Also apply outline options.
+      optionsConsumer.accept(options.outline);
+    };
+  }
+
   DexEncodedMethod getInvokedMethod(DexApplication application, InvokeStatic invoke) {
     DexInspector inspector = new DexInspector(application);
     ClassSubject clazz = inspector.clazz(invoke.getMethod().holder.toSourceString());
@@ -62,31 +71,19 @@
         invokedMethod.proto.returnType.toSourceString(),
         invokedMethod.name.toString(),
         Arrays.stream(invokedMethod.proto.parameters.values)
-            .map(p -> p.toSourceString())
+            .map(DexType::toSourceString)
             .collect(Collectors.toList()));
     assertTrue(method.isPresent());
     return method.getMethod();
   }
 
-  String firstOutlineMethodName(InternalOptions options) {
-    StringBuilder builder = new StringBuilder(options.outline.className);
-    builder.append('.');
-    builder.append(options.outline.methodPrefix);
-    builder.append("0");
-    return builder.toString();
+  private String firstOutlineMethodName() {
+    return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0";
   }
 
-  MethodSignature firstOutlineMethodSignature(
-      String returnType, List<String> parameterTypes, InternalOptions options) {
-    return new MethodSignature(
-        options.outline.className, options.outline.methodPrefix + "0", returnType, parameterTypes);
-  }
-
-  boolean isOutlineMethodName(InternalOptions options, String qualifiedName) {
-    StringBuilder builder = new StringBuilder(options.outline.className);
-    builder.append('.');
-    builder.append(options.outline.methodPrefix);
-    return qualifiedName.indexOf(builder.toString()) == 0;
+  private boolean isOutlineMethodName(String qualifiedName) {
+    String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX;
+    return qualifiedName.indexOf(qualifiedPrefix) == 0;
   }
 
   @Test
@@ -127,14 +124,16 @@
     );
 
     for (int i = 2; i < 6; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int j = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = j;
+        outline.maxSize = j;
+      });
 
-      DexApplication originalApplication = buildApplication(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplication(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -143,10 +142,10 @@
       assertTrue(code.instructions[0] instanceof ConstString);
       assertTrue(code.instructions[1] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-      assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("TestTestTestTest", result);
     }
   }
@@ -192,14 +191,16 @@
     );
 
     for (int i = 2; i < 6; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplication(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplication(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -213,10 +214,10 @@
       }
       assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic);
       InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke];
-      assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("Test1Test2Test3Test4", result);
     }
   }
@@ -258,11 +259,12 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+    });
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -271,10 +273,10 @@
     assertTrue(code.instructions[0] instanceof ConstString);
     assertTrue(code.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("1", result);
   }
 
@@ -322,11 +324,12 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+    });
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -336,10 +339,10 @@
     assertTrue(code.instructions[1] instanceof ConstString);
     assertTrue(code.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke = (InvokeStatic) code.instructions[2];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("TestXTest1TestYTest2Test3", result);
   }
 
@@ -381,14 +384,16 @@
     );
 
     for (int i = 2; i < 4; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -398,17 +403,17 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       }
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       StringBuilder resultBuilder = new StringBuilder();
       for (int j = 0; j < 4; j++) {
         resultBuilder.append(0x7fffffff00000000L);
@@ -455,14 +460,16 @@
     );
 
     for (int i = 2; i < 4; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed method for inspection.
       DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -472,17 +479,17 @@
       if (i < 3) {
         assertTrue(code.instructions[1] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else {
         assertTrue(code.instructions[1] instanceof InvokeVirtual);
         assertTrue(code.instructions[2] instanceof InvokeVirtual);
         assertTrue(code.instructions[3] instanceof InvokeStatic);
         InvokeStatic invoke = (InvokeStatic) code.instructions[3];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       }
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       StringBuilder resultBuilder = new StringBuilder();
       for (int j = 0; j < 4; j++) {
         resultBuilder.append(1.0d);
@@ -525,14 +532,16 @@
     );
 
     for (int i = 2; i < 6; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       // Return the processed main method for inspection.
       DexEncodedMethod mainMethod = getMethod(processedApplication, mainSignature);
@@ -548,18 +557,18 @@
       }
       if (i == 2) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else if (i == 3) {
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       } else {
         assert i == 4 || i == 5;
         InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2];
-        assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+        assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
       }
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("1122", result);
     }
   }
@@ -623,29 +632,30 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 7;
-    options.outline.maxSize = 7;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 7;
+      outline.maxSize = 7;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     DexCode code1 = getMethod(processedApplication, signature1).getCode().asDexCode();
     assertEquals(4, code1.instructions.length);
     assertTrue(code1.instructions[1] instanceof InvokeStatic);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[1];
-    assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
 
     DexCode code2 = getMethod(processedApplication, signature2).getCode().asDexCode();
     assertEquals(5, code2.instructions.length);
     assertTrue(code2.instructions[2] instanceof InvokeStatic);
     InvokeStatic invoke2 = (InvokeStatic) code2.instructions[2];
-    assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("Test1Test1Test1Test1Test2Test2Test2Test2", result);
   }
 
@@ -687,14 +697,16 @@
     );
 
     for (int i = 2; i < 8; i++) {
-      InternalOptions options = createInternalOptions();
-      options.outline.threshold = 1;
-      options.outline.minSize = i;
-      options.outline.maxSize = i;
+      final int finalI = i;
+      Consumer<InternalOptions> options = configureOptions(outline -> {
+        outline.threshold = 1;
+        outline.minSize = finalI;
+        outline.maxSize = finalI;
+      });
 
-      DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-      DexApplication processedApplication = processApplication(originalApplication, options);
-      assertEquals(2, Iterables.size(processedApplication.classes()));
+      AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+      AndroidApp processedApplication = processApplication(originalApplication, options);
+      assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
       DexCode code = getMethod(processedApplication, signature).getCode().asDexCode();
       InvokeStatic invoke;
@@ -711,10 +723,10 @@
           outlineInstructionIndex = 2;
       }
       invoke = (InvokeStatic) code.instructions[outlineInstructionIndex];
-      assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+      assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
       // Run code and check result.
-      String result = runArt(processedApplication, options);
+      String result = runArt(processedApplication);
       assertEquals("Test2Test2", result);
     }
   }
@@ -744,23 +756,24 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     DexCode code = getMethod(processedApplication, signature1).getCode().asDexCode();
     InvokeStatic invoke;
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     invoke = (InvokeStatic) code.instructions[0];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("", result);
   }
 
@@ -817,18 +830,19 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Check that three outlining methods was created.
     DexInspector inspector = new DexInspector(processedApplication);
-    ClassSubject clazz = inspector.clazz(options.outline.className);
+    ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
     assertTrue(clazz.isPresent());
     assertEquals(3, clazz.getDexClass().directMethods().length);
     // Collect the return types of the putlines for the body of method1 and method2.
@@ -842,12 +856,12 @@
     assert r.size() == 2;
     DexType r1 = r.get(0);
     DexType r2 = r.get(1);
-    DexItemFactory factory = processedApplication.dexItemFactory;
+    DexItemFactory factory = inspector.getFactory();
     assertTrue(r1 == factory.voidType && r2 == factory.stringType ||
         r1 == factory.stringType && r2 == factory.voidType);
 
     // Run the code.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("TestTestTestTest", result);
   }
 
@@ -888,37 +902,40 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     final int count = 10;
     // Process the application several times. Each time will outline the previous outline.
     for (int i = 0; i < count; i++) {
       // Build a new application with the Outliner class.
-      DexApplication.Builder appBuilder = processedApplication.builder();
-      originalApplication = appBuilder.build();
+      originalApplication = processedApplication;
       processedApplication = processApplication(originalApplication, options);
-      assertEquals(i + 3, Iterables.size(processedApplication.classes()));
+      assertEquals(i + 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Process the application several times. No more outlining as threshold has been raised.
-    options.outline.threshold = 2;
+    options = configureOptions(outline -> {
+      outline.threshold = 2;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
     for (int i = 0; i < count; i++) {
       // Build a new application with the Outliner class.
-      DexApplication.Builder appBuilder = processedApplication.builder();
-      originalApplication = appBuilder.build();
+      originalApplication = processedApplication;
       processedApplication = processApplication(originalApplication, options);
-      assertEquals(count - 1 + 3, Iterables.size(processedApplication.classes()));
+      assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication));
     }
 
     // Run the application with several levels of outlining.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("TestTestTestTest", result);
   }
 
@@ -950,14 +967,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 5;
-    options.outline.maxSize = 5;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 5;
+      outline.maxSize = 5;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -968,10 +986,10 @@
     assertTrue(code.instructions[1] instanceof MoveResultWide);
     assertTrue(code.instructions[2] instanceof ReturnWide);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run the code and expect a parsable long.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     Long.parseLong(result);
   }
 
@@ -1010,14 +1028,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 4;
-    options.outline.maxSize = 4;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 4;
+      outline.maxSize = 4;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1026,10 +1045,10 @@
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     assertTrue(code.instructions[1] instanceof ReturnObject);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("null", result);
   }
 
@@ -1084,14 +1103,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 4;
-    options.outline.maxSize = 4;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 4;
+      outline.maxSize = 4;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method1 = getMethod(processedApplication, signature1);
@@ -1101,7 +1121,7 @@
     assertTrue(code1.instructions[1] instanceof MoveResult);
     assertTrue(code1.instructions[2] instanceof Return);
     InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0];
-    assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
 
     DexEncodedMethod method2 = getMethod(processedApplication, signature2);
     DexCode code2 = method2.getCode().asDexCode();
@@ -1110,7 +1130,7 @@
     assertEquals(invoke1.getMethod().qualifiedName(), invoke2.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("44", result);
   }
 
@@ -1159,14 +1179,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;  // Outline add, sub and mul.
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;  // Outline add, sub and mul.
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1180,10 +1201,10 @@
     assertTrue(code.instructions[5] instanceof Const4);
     assertTrue(code.instructions[6] instanceof Return);
     InvokeStatic invoke = (InvokeStatic) code.instructions[1];
-    assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName()));
+    assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("4", result);
   }
 
@@ -1211,14 +1232,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1227,10 +1249,10 @@
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     assertTrue(code.instructions[1] instanceof ReturnVoid);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("", result);
   }
 
@@ -1256,14 +1278,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 1;
-    options.outline.minSize = 3;
-    options.outline.maxSize = 3;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 1;
+      outline.minSize = 3;
+      outline.maxSize = 3;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod method = getMethod(processedApplication, signature);
@@ -1272,10 +1295,10 @@
     assertTrue(code.instructions[0] instanceof InvokeStatic);
     assertTrue(code.instructions[1] instanceof ReturnVoid);
     InvokeStatic invoke = (InvokeStatic) code.instructions[0];
-    assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName());
+    assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
 
     // Run code and check result.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals("", result);
   }
 
@@ -1462,14 +1485,15 @@
         "    return-void"
     );
 
-    InternalOptions options = createInternalOptions();
-    options.outline.threshold = 2;
+    Consumer<InternalOptions> options = configureOptions(outline -> {
+      outline.threshold = 2;
+    });
 
-    DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(2, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
+    AndroidApp processedApplication = processApplication(originalApplication, options);
+    assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Verify the code.
-    runDex2Oat(processedApplication, options);
+    runDex2Oat(processedApplication);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/Regress38014736.java b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
index bf89f7f..1f0cd78 100644
--- a/src/test/java/com/android/tools/r8/smali/Regress38014736.java
+++ b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
@@ -5,9 +5,7 @@
 
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
 
@@ -48,10 +46,9 @@
         "invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
         "return-void");
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    String result = runArt(processedApplication, options);
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    String result = runArt(processedApplication);
     // The art runtime changed the way exceptions are printed. Therefore, we only check
     // for the type of the exception and that the message mentions null.
     assertTrue(result.startsWith(StringUtils.joinLines("0", "java.lang.NumberFormatException:")));
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
index 214a3b4..4d13ae0 100644
--- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -12,12 +12,11 @@
 import com.android.tools.r8.code.InvokeVirtual;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.Iterables;
 import org.junit.Test;
 
 public class RunArtSmokeTest extends SmaliTestBase {
@@ -34,10 +33,9 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    assertEquals(1, Iterables.size(processedApplication.classes()));
+    AndroidApp originalApplication = buildApplication(builder);
+    AndroidApp processedApplication = processApplication(originalApplication);
+    assertEquals(1, getNumberOfProgramClasses(processedApplication));
 
     // Return the processed method for inspection.
     DexEncodedMethod main = getMethod(processedApplication, mainSignature);
@@ -50,7 +48,7 @@
     assertTrue(code.instructions[3] instanceof ReturnVoid);
 
     // Run the generated code in Art.
-    String result = runArt(processedApplication, options);
+    String result = runArt(processedApplication);
     assertEquals(StringUtils.lines("Hello, world!"), result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index b3a4060..8e946b6 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -7,20 +7,24 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class SmaliBuildTest extends SmaliTestBase {
 
-  private void checkJavaLangString(DexApplication application, boolean present) {
-    DexInspector inspector = new DexInspector(application);
-    ClassSubject clazz = inspector.clazz("java.lang.String");
-    assertEquals(present, clazz.isPresent());
+  private void checkJavaLangString(AndroidApp application, boolean present) {
+    try {
+      DexInspector inspector = new DexInspector(application);
+      ClassSubject clazz = inspector.clazz("java.lang.String");
+      assertEquals(present, clazz.isPresent());
+    } catch (IOException | ExecutionException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   @Test
@@ -35,12 +39,11 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
     // No libraries added - java.lang.String is not present.
-    DexApplication originalApplication = buildApplication(builder, options);
+    AndroidApp originalApplication = buildApplication(builder);
     checkJavaLangString(originalApplication, false);
 
-    DexApplication processedApplication = processApplication(originalApplication, options);
+    AndroidApp processedApplication = processApplication(originalApplication);
     checkJavaLangString(processedApplication, false);
   }
 
@@ -56,17 +59,17 @@
         "    return-void"
     );
 
-    InternalOptions options = new InternalOptions();
-    AndroidApp app = AndroidApp.builder()
+    AndroidApp originalApp = AndroidApp.builder()
         .addDexProgramData(builder.compile())
         .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
         .build();
 
     // Java standard library added - java.lang.String is present.
-    DexApplication originalApplication = buildApplication(app, options);
-    checkJavaLangString(originalApplication, true);
+    checkJavaLangString(originalApp, true);
 
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    checkJavaLangString(processedApplication, true);
+    AndroidApp processedApplication = processApplication(originalApp);
+
+    // The library method is not part of the output.
+    checkJavaLangString(processedApplication, false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
new file mode 100644
index 0000000..ad1e097
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -0,0 +1,349 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.smali;
+
+import com.android.tools.r8.errors.DexOverflowException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Smali;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import org.antlr.runtime.RecognitionException;
+
+public class SmaliBuilder {
+
+  public static class MethodSignature {
+
+    public final String clazz;
+    public final String name;
+    public final String returnType;
+    public final List<String> parameterTypes;
+
+    public MethodSignature(String clazz, String name, String returnType,
+        List<String> parameterTypes) {
+      this.clazz = clazz;
+      this.name = name;
+      this.returnType = returnType;
+      this.parameterTypes = parameterTypes;
+    }
+
+    public static MethodSignature staticInitializer(String clazz) {
+      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+    }
+
+    @Override
+    public String toString() {
+      return returnType + " " + clazz + "." + name
+          + "(" + StringUtils.join(parameterTypes, ",") + ")";
+    }
+  }
+
+  abstract class Builder {
+
+    String name;
+    String superName;
+    List<String> implementedInterfaces;
+    String sourceFile = null;
+    List<String> source = new ArrayList<>();
+
+    Builder(String name, String superName, List<String> implementedInterfaces) {
+      this.name = name;
+      this.superName = superName;
+      this.implementedInterfaces = implementedInterfaces;
+    }
+
+    protected void appendSuper(StringBuilder builder) {
+      builder.append(".super ");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
+      builder.append("\n");
+    }
+
+    protected void appendImplementedInterfaces(StringBuilder builder) {
+      for (String implementedInterface : implementedInterfaces) {
+        builder.append(".implements ");
+        builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
+        builder.append("\n");
+      }
+    }
+
+    protected void writeSource(StringBuilder builder) {
+      for (String sourceLine : source) {
+        builder.append(sourceLine);
+        builder.append("\n");
+      }
+    }
+  }
+
+  public class ClassBuilder extends Builder {
+
+    ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
+      super(name, superName, implementedInterfaces);
+    }
+
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".class public ");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(name));
+      builder.append("\n");
+      appendSuper(builder);
+      appendImplementedInterfaces(builder);
+      builder.append("\n");
+      if (sourceFile != null) {
+        builder.append(".source \"").append(sourceFile).append("\"\n");
+      }
+      writeSource(builder);
+      return builder.toString();
+    }
+  }
+
+  public class InterfaceBuilder extends Builder {
+
+    InterfaceBuilder(String name, String superName) {
+      super(name, superName, ImmutableList.of());
+    }
+
+    public String toString() {
+      StringBuilder builder = new StringBuilder();
+      builder.append(".class public interface abstract ");
+      builder.append(DescriptorUtils.javaTypeToDescriptor(name));
+      builder.append("\n");
+      appendSuper(builder);
+      appendImplementedInterfaces(builder);
+      builder.append("\n");
+      writeSource(builder);
+      return builder.toString();
+    }
+  }
+
+  private String currentClassName;
+  private final Map<String, Builder> classes = new HashMap<>();
+
+  public SmaliBuilder() {
+    // No default class.
+  }
+
+  public SmaliBuilder(String name) {
+    addClass(name);
+  }
+
+  public SmaliBuilder(String name, String superName) {
+    addClass(name, superName);
+  }
+
+  private List<String> getSource(String clazz) {
+    return classes.get(clazz).source;
+  }
+
+  public String getCurrentClassName() {
+    return currentClassName;
+  }
+
+  public String getCurrentClassDescriptor() {
+    return DescriptorUtils.javaTypeToDescriptor(currentClassName);
+  }
+
+  public void addClass(String name) {
+    addClass(name, "java.lang.Object");
+  }
+
+  public void addClass(String name, String superName) {
+    addClass(name, superName, ImmutableList.of());
+  }
+
+  public void addClass(String name, String superName, List<String> implementedInterfaces) {
+    assert !classes.containsKey(name);
+    currentClassName = name;
+    classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
+  }
+
+  public void addInterface(String name) {
+    addInterface(name, "java.lang.Object");
+  }
+
+  public void addInterface(String name, String superName) {
+    assert !classes.containsKey(name);
+    currentClassName = name;
+    classes.put(name, new InterfaceBuilder(name, superName));
+  }
+
+  public void setSourceFile(String file) {
+    classes.get(currentClassName).sourceFile = file;
+  }
+
+  public void addDefaultConstructor() {
+    String superDescriptor =
+        DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName);
+    addMethodRaw(
+        "  .method public constructor <init>()V",
+        "    .locals 0",
+        "    invoke-direct {p0}, " + superDescriptor + "-><init>()V",
+        "    return-void",
+        "  .end method"
+    );
+  }
+
+  public void addStaticField(String name, String type, String defaultValue) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(".field static ");
+    builder.append(name);
+    builder.append(":");
+    builder.append(type);
+    if (defaultValue != null) {
+      builder.append(" = ");
+      if (type.equals("Ljava/lang/String;")) {
+        builder.append('"');
+        builder.append(defaultValue);
+        builder.append('"');
+      } else {
+        builder.append(defaultValue);
+      }
+    }
+    getSource(currentClassName).add(builder.toString());
+  }
+
+  public void addStaticField(String name, String type) {
+    addStaticField(name, type, null);
+  }
+
+  public void addInstanceField(String name, String type) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(".field ");
+    builder.append(name);
+    builder.append(":");
+    builder.append(type);
+    getSource(currentClassName).add(builder.toString());
+  }
+
+  private MethodSignature addMethod(String flags, String returnType, String name,
+      List<String> parameters, int locals, String code) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(".method ");
+    if (flags != null && flags.length() > 0) {
+      builder.append(flags);
+      builder.append(" ");
+    }
+    builder.append(name);
+    builder.append("(");
+    for (String parameter : parameters) {
+      builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
+    }
+    builder.append(")");
+    builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
+    builder.append("\n");
+    if (locals >= 0) {
+      builder.append(".locals ");
+      builder.append(locals);
+      builder.append("\n\n");
+      assert code != null;
+      builder.append(code);
+    } else {
+      assert code == null;
+    }
+    builder.append(".end method");
+    getSource(currentClassName).add(builder.toString());
+    return new MethodSignature(currentClassName, name, returnType, parameters);
+  }
+
+  public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
+      int locals, String... instructions) {
+    StringBuilder builder = new StringBuilder();
+    for (String instruction : instructions) {
+      builder.append(instruction);
+      builder.append("\n");
+    }
+    return addStaticMethod(returnType, name, parameters, locals, builder.toString());
+  }
+
+  public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
+      int locals, String code) {
+    return addStaticMethod("", returnType, name, parameters, locals, code);
+  }
+
+  public MethodSignature addStaticInitializer(int locals, String... instructions) {
+    StringBuilder builder = new StringBuilder();
+    for (String instruction : instructions) {
+      builder.append(instruction);
+      builder.append("\n");
+    }
+    return addStaticInitializer(locals, builder.toString());
+  }
+
+  public MethodSignature addStaticInitializer(int locals, String code) {
+    return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+  }
+
+  private MethodSignature addStaticMethod(String flags, String returnType, String name,
+      List<String> parameters, int locals, String code) {
+    StringBuilder builder = new StringBuilder();
+    return addMethod("public static " + flags, returnType, name, parameters, locals, code);
+  }
+
+  public MethodSignature addAbstractMethod(
+      String returnType, String name, List<String> parameters) {
+    return addMethod("public abstract", returnType, name, parameters, -1, null);
+  }
+
+  public MethodSignature addInstanceMethod(String returnType, String name,
+      List<String> parameters,
+      int locals, String... instructions) {
+    StringBuilder builder = new StringBuilder();
+    for (String instruction : instructions) {
+      builder.append(instruction);
+      builder.append("\n");
+    }
+    return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
+  }
+
+  public MethodSignature addInstanceMethod(String returnType, String name,
+      List<String> parameters,
+      int locals, String code) {
+    return addMethod("public", returnType, name, parameters, locals, code);
+  }
+
+  public MethodSignature addMainMethod(int locals, String... instructions) {
+    return addStaticMethod(
+        "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions);
+  }
+
+  public void addMethodRaw(String... source) {
+    StringBuilder builder = new StringBuilder();
+    for (String line : source) {
+      builder.append(line);
+      builder.append("\n");
+    }
+    getSource(currentClassName).add(builder.toString());
+  }
+
+  public List<String> buildSource() {
+    List<String> result = new ArrayList<>(classes.size());
+    for (String clazz : classes.keySet()) {
+      Builder classBuilder = classes.get(clazz);
+      result.add(classBuilder.toString());
+    }
+    return result;
+  }
+
+  public byte[] compile()
+      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+    return Smali.compile(buildSource());
+  }
+
+  public AndroidApp build()
+      throws IOException, RecognitionException, DexOverflowException, ExecutionException {
+    return AndroidApp.fromDexProgramData(compile());
+  }
+
+
+  @Override
+  public String toString() {
+    return String.join("\n\n", buildSource());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
index b35e768..3fe8da9 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -6,16 +6,12 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.DexOverflowException;
-import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Smali;
-import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
@@ -27,12 +23,7 @@
   // Run the provided smali through R8 smali disassembler and expect the exact same output.
   void roundTripRawSmali(String smali) {
     try {
-      DexApplication application =
-          new ApplicationReader(
-                  AndroidApp.fromDexProgramData(Smali.compile(smali)),
-                  new InternalOptions(),
-                  new Timing("SmaliTest"))
-              .read();
+      AndroidApp application = AndroidApp.fromDexProgramData(Smali.compile(smali));
       assertEquals(smali, SmaliWriter.smali(application, new InternalOptions()));
     } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
       throw new RuntimeException(e);
@@ -41,7 +32,7 @@
 
   @Test
   public void simpleSmokeTest() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int", Collections.singletonList("int"),
         4,
         "    const/4 v0, 1           ",
@@ -81,7 +72,7 @@
 
   @Test
   public void sparseSwitchTest() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int", Collections.singletonList("int"),
         0,
         "    sparse-switch v0, :sparse_switch_data",
@@ -137,7 +128,7 @@
 
   @Test
   public void packedSwitchTest() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int", Collections.singletonList("int"),
         0,
         "    packed-switch v0, :packed_switch_data",
@@ -193,7 +184,7 @@
 
   @Test
   public void fillArrayDataTest8Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -236,7 +227,7 @@
 
   @Test
   public void fillArrayDataTest16Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -279,7 +270,7 @@
 
   @Test
   public void fillArrayDataTest32Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -322,7 +313,7 @@
 
   @Test
   public void fillArrayDataTest64Bit() {
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         "int[]", ImmutableList.of(),
         2,
         "    const/4 v1, 3",
@@ -368,8 +359,7 @@
     SmaliBuilder builder = new SmaliBuilder();
     builder.addInterface("Test");
     builder.addAbstractMethod("int", "test", ImmutableList.of());
-    DexApplication application = buildApplication(builder);
-    assertEquals(1, Iterables.size(application.classes()));
+    AndroidApp application = buildApplication(builder);
 
     String expected =
         ".class public interface abstract LTest;\n" +
@@ -391,8 +381,7 @@
     SmaliBuilder builder = new SmaliBuilder();
     builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List"));
     builder.addAbstractMethod("int", "test", ImmutableList.of());
-    DexApplication application = buildApplication(builder);
-    assertEquals(1, Iterables.size(application.classes()));
+    AndroidApp application = buildApplication(builder);
 
     String expected =
         ".class public LTest;\n" +
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 2d2f887..4949153 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -8,8 +8,8 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.Resource;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
@@ -18,39 +18,21 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppOutputSink;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.Smali;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 import org.antlr.runtime.RecognitionException;
 
@@ -60,414 +42,20 @@
   public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME;
   public static final String DEFAULT_METHOD_NAME = "method";
 
-  public static class MethodSignature {
-
-    public final String clazz;
-    public final String name;
-    public final String returnType;
-    public final List<String> parameterTypes;
-
-    public MethodSignature(String clazz, String name, String returnType,
-        List<String> parameterTypes) {
-      this.clazz = clazz;
-      this.name = name;
-      this.returnType = returnType;
-      this.parameterTypes = parameterTypes;
-    }
-
-    public static MethodSignature staticInitializer(String clazz) {
-      return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
-    }
-
-    @Override
-    public String toString() {
-      return returnType + " " + clazz + "." + name
-          + "(" + StringUtils.join(parameterTypes, ",") + ")";
-    }
-  }
-
-  public static class SmaliBuilder {
-
-    abstract class Builder {
-
-      String name;
-      String superName;
-      List<String> implementedInterfaces;
-      String sourceFile = null;
-      List<String> source = new ArrayList<>();
-
-      Builder(String name, String superName, List<String> implementedInterfaces) {
-        this.name = name;
-        this.superName = superName;
-        this.implementedInterfaces = implementedInterfaces;
-      }
-
-      protected void appendSuper(StringBuilder builder) {
-        builder.append(".super ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(superName));
-        builder.append("\n");
-      }
-
-      protected void appendImplementedInterfaces(StringBuilder builder) {
-        for (String implementedInterface : implementedInterfaces) {
-          builder.append(".implements ");
-          builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface));
-          builder.append("\n");
-        }
-      }
-
-      protected void writeSource(StringBuilder builder) {
-        for (String sourceLine : source) {
-          builder.append(sourceLine);
-          builder.append("\n");
-        }
-      }
-    }
-
-    public class ClassBuilder extends Builder {
-
-      ClassBuilder(String name, String superName, List<String> implementedInterfaces) {
-        super(name, superName, implementedInterfaces);
-      }
-
-      public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(".class public ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(name));
-        builder.append("\n");
-        appendSuper(builder);
-        appendImplementedInterfaces(builder);
-        builder.append("\n");
-        if (sourceFile != null) {
-          builder.append(".source \"").append(sourceFile).append("\"\n");
-        }
-        writeSource(builder);
-        return builder.toString();
-      }
-    }
-
-    public class InterfaceBuilder extends Builder {
-
-      InterfaceBuilder(String name, String superName) {
-        super(name, superName, ImmutableList.of());
-      }
-
-      public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append(".class public interface abstract ");
-        builder.append(DescriptorUtils.javaTypeToDescriptor(name));
-        builder.append("\n");
-        appendSuper(builder);
-        appendImplementedInterfaces(builder);
-        builder.append("\n");
-        writeSource(builder);
-        return builder.toString();
-      }
-    }
-
-    private String currentClassName;
-    private final Map<String, Builder> classes = new HashMap<>();
-
-    public SmaliBuilder() {
-      // No default class.
-    }
-
-    public SmaliBuilder(String name) {
-      addClass(name);
-    }
-
-    public SmaliBuilder(String name, String superName) {
-      addClass(name, superName);
-    }
-
-    private List<String> getSource(String clazz) {
-      return classes.get(clazz).source;
-    }
-
-    public String getCurrentClassName() {
-      return currentClassName;
-    }
-
-    public String getCurrentClassDescriptor() {
-      return DescriptorUtils.javaTypeToDescriptor(currentClassName);
-    }
-
-    public void addClass(String name) {
-      addClass(name, "java.lang.Object");
-    }
-
-    public void addClass(String name, String superName) {
-      addClass(name, superName, ImmutableList.of());
-    }
-
-    public void addClass(String name, String superName, List<String> implementedInterfaces) {
-      assert !classes.containsKey(name);
-      currentClassName = name;
-      classes.put(name, new ClassBuilder(name, superName, implementedInterfaces));
-    }
-
-    public void addInterface(String name) {
-      addInterface(name, "java.lang.Object");
-    }
-
-    public void addInterface(String name, String superName) {
-      assert !classes.containsKey(name);
-      currentClassName = name;
-      classes.put(name, new InterfaceBuilder(name, superName));
-    }
-
-    public void setSourceFile(String file) {
-      classes.get(currentClassName).sourceFile = file;
-    }
-
-    public void addDefaultConstructor() {
-      String superDescriptor =
-          DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName);
-      addMethodRaw(
-          "  .method public constructor <init>()V",
-          "    .locals 0",
-          "    invoke-direct {p0}, " + superDescriptor + "-><init>()V",
-          "    return-void",
-          "  .end method"
-      );
-    }
-
-    public void addStaticField(String name, String type, String defaultValue) {
-      StringBuilder builder = new StringBuilder();
-      builder.append(".field static ");
-      builder.append(name);
-      builder.append(":");
-      builder.append(type);
-      if (defaultValue != null) {
-        builder.append(" = ");
-        if (type.equals("Ljava/lang/String;")) {
-          builder.append('"');
-          builder.append(defaultValue);
-          builder.append('"');
-        } else {
-          builder.append(defaultValue);
-        }
-      }
-      getSource(currentClassName).add(builder.toString());
-    }
-
-    public void addStaticField(String name, String type) {
-      addStaticField(name, type, null);
-    }
-
-    public void addInstanceField(String name, String type) {
-      StringBuilder builder = new StringBuilder();
-      builder.append(".field ");
-      builder.append(name);
-      builder.append(":");
-      builder.append(type);
-      getSource(currentClassName).add(builder.toString());
-    }
-
-    private MethodSignature addMethod(String flags, String returnType, String name,
-        List<String> parameters, int locals, String code) {
-      StringBuilder builder = new StringBuilder();
-      builder.append(".method ");
-      if (flags != null && flags.length() > 0) {
-        builder.append(flags);
-        builder.append(" ");
-      }
-      builder.append(name);
-      builder.append("(");
-      for (String parameter : parameters) {
-        builder.append(DescriptorUtils.javaTypeToDescriptor(parameter));
-      }
-      builder.append(")");
-      builder.append(DescriptorUtils.javaTypeToDescriptor(returnType));
-      builder.append("\n");
-      if (locals >= 0) {
-        builder.append(".locals ");
-        builder.append(locals);
-        builder.append("\n\n");
-        assert code != null;
-        builder.append(code);
-      } else {
-        assert code == null;
-      }
-      builder.append(".end method");
-      getSource(currentClassName).add(builder.toString());
-      return new MethodSignature(currentClassName, name, returnType, parameters);
-    }
-
-    public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
-        int locals, String... instructions) {
-      StringBuilder builder = new StringBuilder();
-      for (String instruction : instructions) {
-        builder.append(instruction);
-        builder.append("\n");
-      }
-      return addStaticMethod(returnType, name, parameters, locals, builder.toString());
-    }
-
-    public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
-        int locals, String code) {
-      return addStaticMethod("", returnType, name, parameters, locals, code);
-    }
-
-    public MethodSignature addStaticInitializer(int locals, String... instructions) {
-      StringBuilder builder = new StringBuilder();
-      for (String instruction : instructions) {
-        builder.append(instruction);
-        builder.append("\n");
-      }
-      return addStaticInitializer(locals, builder.toString());
-    }
-
-    public MethodSignature addStaticInitializer(int locals, String code) {
-      return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
-    }
-
-    private MethodSignature addStaticMethod(String flags, String returnType, String name,
-        List<String> parameters, int locals, String code) {
-      StringBuilder builder = new StringBuilder();
-      return addMethod("public static " + flags, returnType, name, parameters, locals, code);
-    }
-
-    public MethodSignature addAbstractMethod(
-        String returnType, String name, List<String> parameters) {
-      return addMethod("public abstract", returnType, name, parameters, -1, null);
-    }
-
-    public MethodSignature addInstanceMethod(String returnType, String name,
-        List<String> parameters,
-        int locals, String... instructions) {
-      StringBuilder builder = new StringBuilder();
-      for (String instruction : instructions) {
-        builder.append(instruction);
-        builder.append("\n");
-      }
-      return addInstanceMethod(returnType, name, parameters, locals, builder.toString());
-    }
-
-    public MethodSignature addInstanceMethod(String returnType, String name,
-        List<String> parameters,
-        int locals, String code) {
-      return addMethod("public", returnType, name, parameters, locals, code);
-    }
-
-    public MethodSignature addMainMethod(int locals, String... instructions) {
-      return addStaticMethod(
-          "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions);
-    }
-
-    public void addMethodRaw(String... source) {
-      StringBuilder builder = new StringBuilder();
-      for (String line : source) {
-        builder.append(line);
-        builder.append("\n");
-      }
-      getSource(currentClassName).add(builder.toString());
-    }
-
-    public List<String> buildSource() {
-      List<String> result = new ArrayList<>(classes.size());
-      for (String clazz : classes.keySet()) {
-        Builder classBuilder = classes.get(clazz);
-        result.add(classBuilder.toString());
-      }
-      return result;
-    }
-
-    public byte[] compile()
-        throws IOException, RecognitionException, DexOverflowException, ExecutionException {
-      return Smali.compile(buildSource());
-    }
-
-    public AndroidApp build()
-        throws IOException, RecognitionException, DexOverflowException, ExecutionException {
-      return AndroidApp.fromDexProgramData(compile());
-    }
-
-
-    @Override
-    public String toString() {
-      return String.join("\n\n", buildSource());
-    }
-  }
-
-  public class TestApplication {
-
-    public final DexApplication application;
-    public final DexEncodedMethod method;
-    public final IRCode code;
-    public final List<IRCode> additionalCode;
-    public final ValueNumberGenerator valueNumberGenerator;
-    public final InternalOptions options;
-
-    public TestApplication(
-        DexApplication application,
-        DexEncodedMethod method,
-        IRCode code,
-        ValueNumberGenerator valueNumberGenerator,
-        InternalOptions options) {
-      this(application, method, code, null, valueNumberGenerator, options);
-    }
-
-    public TestApplication(
-        DexApplication application,
-        DexEncodedMethod method,
-        IRCode code,
-        List<IRCode> additionalCode,
-        ValueNumberGenerator valueNumberGenerator,
-        InternalOptions options) {
-      this.application = application;
-      this.method = method;
-      this.code = code;
-      this.additionalCode = additionalCode;
-      this.valueNumberGenerator = valueNumberGenerator;
-      this.options = options;
-    }
-
-    public int countArgumentInstructions() {
-      int count = 0;
-      ListIterator<Instruction> iterator = code.blocks.get(0).listIterator();
-      while (iterator.next().isArgument()) {
-        count++;
-      }
-      return count;
-    }
-
-    public InstructionListIterator listIteratorAt(BasicBlock block, int index) {
-      InstructionListIterator iterator = block.listIterator();
-      for (int i = 0; i < index; i++) {
-        iterator.next();
-      }
-      return iterator;
-    }
-
-    public String run() throws DexOverflowException {
-      AppInfo appInfo = new AppInfo(application);
-      IRConverter converter = new IRConverter(appInfo, options);
-      converter.replaceCodeForTesting(method, code);
-      return runArt(application, options);
-    }
-  }
-
-  protected DexApplication buildApplication(SmaliBuilder builder) {
-    return buildApplication(builder, new InternalOptions());
-  }
-
-  protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) {
+  protected AndroidApp buildApplication(SmaliBuilder builder) {
     try {
-      return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options);
-    } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
+      return AndroidApp.fromDexProgramData(builder.compile());
+    } catch (IOException | RecognitionException | DexOverflowException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
 
-  protected DexApplication buildApplicationWithAndroidJar(
-      SmaliBuilder builder, InternalOptions options) {
+  protected AndroidApp buildApplicationWithAndroidJar(SmaliBuilder builder) {
     try {
-      AndroidApp input = AndroidApp.builder()
+      return AndroidApp.builder()
           .addDexProgramData(builder.compile())
           .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
           .build();
-      return buildApplication(input, options);
     } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) {
       throw new RuntimeException(e);
     }
@@ -482,10 +70,15 @@
     }
   }
 
-  protected DexApplication processApplication(DexApplication application, InternalOptions options) {
+  protected AndroidApp processApplication(AndroidApp application) {
+    return processApplication(application, null);
+  }
+
+  protected AndroidApp processApplication(AndroidApp application,
+      Consumer<InternalOptions> optionsConsumer) {
     try {
-      return ToolHelper.optimizeWithR8(application, options);
-    } catch (IOException | CompilationException | ExecutionException e) {
+      return ToolHelper.runR8(application, optionsConsumer);
+    } catch (IOException | CompilationException e) {
       throw new RuntimeException(e);
     }
   }
@@ -535,29 +128,6 @@
     }
   }
 
-  protected DexEncodedMethod getMethod(
-      DexInspector inspector,
-      String className,
-      String returnType,
-      String methodName,
-      List<String> parameters) {
-    ClassSubject clazz = inspector.clazz(className);
-    assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(returnType, methodName, parameters);
-    assertTrue(method.isPresent());
-    return method.getMethod();
-  }
-
-  protected DexEncodedMethod getMethod(
-      DexApplication application,
-      String className,
-      String returnType,
-      String methodName,
-      List<String> parameters) {
-    DexInspector inspector = new DexInspector(application);
-    return getMethod(inspector, className, returnType, methodName, parameters);
-  }
-
   protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) {
     try {
       DexInspector inspector = new DexInspector(appPath);
@@ -572,7 +142,7 @@
     }
   }
 
-  protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) {
+  protected DexEncodedMethod getMethod(AndroidApp application, MethodSignature signature) {
     return getMethod(application,
         signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
   }
@@ -588,16 +158,45 @@
    * @param instructions instructions for the method
    * @return the processed method for inspection
    */
-  public DexApplication singleMethodApplication(String returnType, List<String> parameters,
+  public AndroidApp singleMethodApplication(String returnType, List<String> parameters,
       int locals, String... instructions) {
     // Build a one class method.
     SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
     builder.addStaticMethod(returnType, DEFAULT_METHOD_NAME, parameters, locals, instructions);
 
     // Read the one class method as an application.
-    DexApplication application = buildApplication(builder);
-    assertEquals(1, Iterables.size(application.classes()));
-    return application;
+    return buildApplication(builder);
+  }
+
+  private int getNumberOfClassesForResources(Iterable<Resource> resources) {
+    int count = 0;
+    for (Resource resource : resources) {
+      Collection<String> descriptors = resource.getClassDescriptors();
+      if (descriptors == null) {
+        throw new IllegalStateException("Cannot count classes in application without descriptors.");
+      }
+      count += descriptors.size();
+    }
+    return count;
+  }
+
+  protected int getNumberOfProgramClasses(AndroidApp application) {
+    try {
+      return getNumberOfClassesForResources(application.getClassProgramResources())
+          + getNumberOfClassesForResources(application.getDexProgramResources());
+    } catch (IOException e) {
+      return -1;
+    }
+  }
+
+  protected AppInfo getAppInfo(AndroidApp application) {
+    try {
+      DexApplication dexApplication = new ApplicationReader(application, new InternalOptions(),
+          new Timing("SmaliTest.getAppInfo")).read();
+      return new AppInfo(dexApplication);
+    } catch (IOException | ExecutionException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   /**
@@ -616,30 +215,29 @@
     InternalOptions options = new InternalOptions();
 
     // Build a one class application.
-    DexApplication application = singleMethodApplication(
+    AndroidApp application = singleMethodApplication(
         returnType, parameters, locals, instructions);
 
     // Process the application with R8.
-    DexApplication processdApplication = processApplication(application, options);
-    assertEquals(1, Iterables.size(processdApplication.classes()));
+    AndroidApp processdApplication = processApplication(application);
+    assertEquals(1, getNumberOfProgramClasses(processdApplication));
 
     // Return the processed method for inspection.
     return getMethod(
         processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters);
   }
 
-  public String runArt(DexApplication application, InternalOptions options)
-      throws DexOverflowException {
-    return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
+  public String runArt(AndroidApp application) throws DexOverflowException {
+    return runArt(application, DEFAULT_MAIN_CLASS_NAME);
   }
 
-  public String runArt(DexApplication application, InternalOptions options, String mainClass)
+
+  public String runArt(AndroidApp application, String mainClass)
       throws DexOverflowException {
     try {
-      AndroidApp app = writeDex(application, options);
       Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
       // TODO(sgjesse): Pass in a unique temp directory for each run.
-      app.writeToZip(out, OutputMode.Indexed);
+      application.writeToZip(out, OutputMode.Indexed);
       return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
     } catch (IOException e) {
       throw new RuntimeException(e);
@@ -654,36 +252,15 @@
     }
   }
 
-  public void runDex2Oat(DexApplication application, InternalOptions options)
+  public void runDex2Oat(AndroidApp application)
       throws DexOverflowException {
     try {
-      AndroidApp app = writeDex(application, options);
       Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip");
       Path oatFile = temp.getRoot().toPath().resolve("oat-file");
-      app.writeToZip(dexOut, OutputMode.Indexed);
+      application.writeToZip(dexOut, OutputMode.Indexed);
       ToolHelper.runDex2Oat(dexOut, oatFile);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
   }
-
-  public AndroidApp writeDex(DexApplication application, InternalOptions options)
-      throws DexOverflowException {
-    try {
-      AndroidAppOutputSink compatSink = new AndroidAppOutputSink();
-      R8.writeApplication(
-          Executors.newSingleThreadExecutor(),
-          application,
-          compatSink,
-          null,
-          NamingLens.getIdentityLens(),
-          null,
-          options);
-      compatSink.close();
-      return compatSink.build();
-    } catch (ExecutionException | IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
deleted file mode 100644
index 3fc329c..0000000
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ /dev/null
@@ -1,570 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.smali;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Const;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstHigh16;
-import com.android.tools.r8.code.IfEq;
-import com.android.tools.r8.code.IfEqz;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.PackedSwitch;
-import com.android.tools.r8.code.SparseSwitch;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.shaking.FilteredClassPath;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Test;
-
-public class SwitchRewritingTest extends SmaliTestBase {
-
-  private boolean twoCaseWillUsePackedSwitch(int key1, int key2) {
-    assert key1 != key2;
-    return Math.abs((long) key1 - (long) key2) == 1;
-  }
-
-  private boolean some16BitConst(Instruction instruction) {
-    return instruction instanceof Const4
-        || instruction instanceof ConstHigh16
-        || instruction instanceof Const;
-  }
-  private void runSingleCaseDexTest(boolean packed, int key) {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-    String switchInstruction;
-    String switchData;
-    if (packed) {
-      switchInstruction = "packed-switch";
-      switchData = StringUtils.join(
-          "\n",
-          "  :switch_data",
-          "  .packed-switch " + key,
-          "    :case_0",
-          "  .end packed-switch");
-    } else {
-      switchInstruction = "sparse-switch";
-      switchData = StringUtils.join(
-          "\n",
-          "  :switch_data",
-          "  .sparse-switch",
-          "    " + key + " -> :case_0",
-          "  .end sparse-switch");
-    }
-    MethodSignature signature = builder.addStaticMethod(
-        "int",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    " + switchInstruction + " p0, :switch_data",
-        "    const/4 p0, 0x5",
-        "    goto :return",
-        "  :case_0",
-        "    const/4 p0, 0x3",
-        "  :return",
-        "    return p0",
-        switchData);
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)I",
-        "    move-result         v1",
-        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
-        "    return-void"
-    );
-
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-
-    if (key == 0) {
-      assertEquals(5, code.instructions.length);
-      assertTrue(code.instructions[0] instanceof IfEqz);
-    } else {
-      assertEquals(6, code.instructions.length);
-      assertTrue(some16BitConst(code.instructions[0]));
-      assertTrue(code.instructions[1] instanceof IfEq);
-    }
-  }
-
-  @Test
-  public void singleCaseDex() {
-    for (boolean packed : new boolean[]{true, false}) {
-      runSingleCaseDexTest(packed, Integer.MIN_VALUE);
-      runSingleCaseDexTest(packed, -1);
-      runSingleCaseDexTest(packed, 0);
-      runSingleCaseDexTest(packed, 1);
-      runSingleCaseDexTest(packed, Integer.MAX_VALUE);
-    }
-  }
-
-  private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    MethodSignature signature = builder.addStaticMethod(
-        "int",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    sparse-switch p0, :sparse_switch_data",
-        "    const/4 v0, 0x5",
-        "    goto :return",
-        "  :case_1",
-        "    const/4 v0, 0x3",
-        "    goto :return",
-        "  :case_2",
-        "    const/4 v0, 0x4",
-        "  :return",
-        "    return v0",
-        "  :sparse_switch_data",
-        "  .sparse-switch",
-        "    " + key1 + " -> :case_1",
-        "    " + key2 + " -> :case_2",
-        "  .end sparse-switch");
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)I",
-        "    move-result         v1",
-        "    invoke-virtual      { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
-        "    return-void"
-    );
-
-    InternalOptions options = new InternalOptions();
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (twoCaseWillUsePackedSwitch(key1, key2)) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      if (key1 == 0) {
-        assertTrue(code.instructions[0] instanceof IfEqz);
-      } else {
-        // Const instruction before if.
-        assertTrue(code.instructions[1] instanceof IfEq);
-      }
-    }
-  }
-
-  @Test
-  public void twoCaseSparseToPackedOrIfsDex() {
-    for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedOrIfsDexTest(0, delta);
-      runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0);
-      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
-    }
-    runTwoCaseSparseToPackedOrIfsDexTest(-1, 1);
-    runTwoCaseSparseToPackedOrIfsDexTest(-2, 1);
-    runTwoCaseSparseToPackedOrIfsDexTest(-1, 2);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
-  }
-
-  private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys,
-      Integer additionalLastKey) throws Exception {
-    SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
-    StringBuilder switchSource = new StringBuilder();
-    StringBuilder targetCode = new StringBuilder();
-    for (int i = 0; i < totalKeys; i++) {
-      String caseLabel = "case_" + i;
-      switchSource.append("    " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n");
-      targetCode.append("  :" + caseLabel + "\n");
-      targetCode.append("    goto :return\n");
-    }
-    if (additionalLastKey != null) {
-      String caseLabel = "case_" + totalKeys;
-      switchSource.append("    " + additionalLastKey + " -> :" + caseLabel + "\n");
-      targetCode.append("  :" + caseLabel + "\n");
-      targetCode.append("    goto :return\n");
-    }
-
-    MethodSignature signature = builder.addStaticMethod(
-        "void",
-        DEFAULT_METHOD_NAME,
-        ImmutableList.of("int"),
-        0,
-        "    sparse-switch p0, :sparse_switch_data",
-        "    goto :return",
-        targetCode.toString(),
-        "  :return",
-        "    return-void",
-        "  :sparse_switch_data",
-        "  .sparse-switch",
-        switchSource.toString(),
-        "  .end sparse-switch");
-
-    builder.addMainMethod(
-        2,
-        "    sget-object         v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
-        "    const/4             v1, 0",
-        "    invoke-static       { v1 }, LTest;->method(I)V",
-        "    return-void"
-    );
-
-    InternalOptions options = new InternalOptions();
-    options.verbose = true;
-    options.printTimes = true;
-    DexApplication originalApplication = buildApplication(builder, options);
-    DexApplication processedApplication = processApplication(originalApplication, options);
-    DexEncodedMethod method = getMethod(processedApplication, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (keyStep <= 2) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      assertTrue(code.instructions[0] instanceof SparseSwitch);
-    }
-  }
-
-  @Test
-  public void twoMonsterSparseToPackedDex() throws Exception {
-    runLargerSwitchDexTest(0, 1, 100, null);
-    runLargerSwitchDexTest(0, 2, 100, null);
-    runLargerSwitchDexTest(0, 3, 100, null);
-    runLargerSwitchDexTest(100, 100, 100, null);
-    runLargerSwitchDexTest(-10000, 100, 100, null);
-    runLargerSwitchDexTest(-10000, 200, 100, 10000);
-    runLargerSwitchDexTest(
-        Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
-
-    // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches.
-    // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null);
-  }
-
-  private void runSingleCaseJarTest(boolean packed, int key) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    String switchCode;
-    if (packed) {
-      switchCode = StringUtils.join(
-          "\n",
-          "    tableswitch " + key,
-          "      case_0",
-          "      default : case_default");
-    } else {
-      switchCode = StringUtils.join(
-          "\n",
-          "    lookupswitch",
-          "      " + key + " : case_0",
-          "      default : case_default");
-    }
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        switchCode,
-        "  case_0:",
-        "    iconst_3",
-        "    goto return_",
-        "  case_default:",
-        "    ldc 5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
-    MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
-    DexEncodedMethod method = getMethod(app, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (key == 0) {
-      assertEquals(5, code.instructions.length);
-      assertTrue(code.instructions[0] instanceof IfEqz);
-    } else {
-      assertEquals(6, code.instructions.length);
-      assertTrue(code.instructions[1] instanceof IfEq);
-    }
-  }
-
-  @Test
-  public void singleCaseJar() throws Exception {
-    for (boolean packed : new boolean[]{true, false}) {
-      runSingleCaseJarTest(packed, Integer.MIN_VALUE);
-      runSingleCaseJarTest(packed, -1);
-      runSingleCaseJarTest(packed, 0);
-      runSingleCaseJarTest(packed, 1);
-      runSingleCaseJarTest(packed, Integer.MAX_VALUE);
-    }
-  }
-
-  private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        "    lookupswitch",
-        "      " + key1 + " : case_1",
-        "      " + key2 + " : case_2",
-        "      default : case_default",
-        "  case_1:",
-        "    iconst_3",
-        "    goto return_",
-        "  case_2:",
-        "    iconst_4",
-        "    goto return_",
-        "  case_default:",
-        "    iconst_5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
-    MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
-    DexEncodedMethod method = getMethod(app, signature);
-    DexCode code = method.getCode().asDexCode();
-    if (twoCaseWillUsePackedSwitch(key1, key2)) {
-      assertTrue(code.instructions[0] instanceof PackedSwitch);
-    } else {
-      if (key1 == 0) {
-        assertTrue(code.instructions[0] instanceof IfEqz);
-      } else {
-        // Const instruction before if.
-        assertTrue(code.instructions[1] instanceof IfEq);
-      }
-    }
-  }
-
-  @Test
-  public void twoCaseSparseToPackedJar() throws Exception {
-    for (int delta = 1; delta <= 3; delta++) {
-      runTwoCaseSparseToPackedJarTest(0, delta);
-      runTwoCaseSparseToPackedJarTest(-delta, 0);
-      runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta);
-      runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE);
-    }
-    runTwoCaseSparseToPackedJarTest(-1, 1);
-    runTwoCaseSparseToPackedJarTest(-2, 1);
-    runTwoCaseSparseToPackedJarTest(-1, 2);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE);
-    runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1);
-  }
-
-  private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys,
-      Integer additionalLastKey) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    StringBuilder switchSource = new StringBuilder();
-    StringBuilder targetCode = new StringBuilder();
-    for (int i = 0; i < totalKeys; i++) {
-      String caseLabel = "case_" + i;
-      switchSource.append("      " + (firstKey + i * keyStep) + " : " + caseLabel + "\n");
-      targetCode.append("  " + caseLabel + ":\n");
-      targetCode.append("    ldc " + i + "\n");
-      targetCode.append("    goto return_\n");
-    }
-    if (additionalLastKey != null) {
-      String caseLabel = "case_" + totalKeys;
-      switchSource.append("      " + additionalLastKey + " : " + caseLabel + "\n");
-      targetCode.append("  " + caseLabel + ":\n");
-      targetCode.append("    ldc " + totalKeys + "\n");
-      targetCode.append("    goto return_\n");
-    }
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload 0",
-        "  lookupswitch",
-        switchSource.toString(),
-        "      default : case_default",
-        targetCode.toString(),
-        "  case_default:",
-        "    iconst_5",
-        "  return_:",
-        "    ireturn");
-
-    clazz.addMainMethod(
-        "    .limit stack 2",
-        "    .limit locals 1",
-        "    getstatic java/lang/System/out Ljava/io/PrintStream;",
-        "    ldc 2",
-        "    invokestatic Test/test(I)I",
-        "    invokevirtual java/io/PrintStream/print(I)V",
-        "    return");
-
-    DexApplication app = builder.read();
-    app = ToolHelper.optimizeWithR8(app, new InternalOptions());
-
-    MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int"));
-    DexEncodedMethod method = getMethod(app, signature);
-    DexCode code = method.getCode().asDexCode();
-    int packedSwitchCount = 0;
-    int sparseSwitchCount = 0;
-    for (Instruction instruction : code.instructions) {
-      if (instruction instanceof PackedSwitch) {
-        packedSwitchCount++;
-      }
-      if (instruction instanceof SparseSwitch) {
-        sparseSwitchCount++;
-      }
-    }
-    if (keyStep <= 2) {
-      assertEquals(1, packedSwitchCount);
-      assertEquals(0, sparseSwitchCount);
-    } else {
-      assertEquals(0, packedSwitchCount);
-      assertEquals(1, sparseSwitchCount);
-    }
-  }
-
-  @Test
-  public void largerSwitchJar() throws Exception {
-    runLargerSwitchJarTest(0, 1, 100, null);
-    runLargerSwitchJarTest(0, 2, 100, null);
-    runLargerSwitchJarTest(0, 3, 100, null);
-    runLargerSwitchJarTest(100, 100, 100, null);
-    runLargerSwitchJarTest(-10000, 100, 100, null);
-    runLargerSwitchJarTest(-10000, 200, 100, 10000);
-    runLargerSwitchJarTest(
-        Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE);
-
-    // This is the maximal value possible with Jasmin with the generated code above. It depends on
-    // the source, so making smaller source can raise this limit. However we never get close to the
-    // class file max.
-    runLargerSwitchJarTest(0, 1, 5503, null);
-  }
-
-  private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs,
-      int expectedPackedSwitches, int expectedSparceSwitches) throws Exception {
-    JasminBuilder builder = new JasminBuilder();
-    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
-
-    StringBuilder x = new StringBuilder();
-    StringBuilder y = new StringBuilder();
-    for (Integer key : keys) {
-      x.append(key).append(" : case_").append(key).append("\n");
-      y.append("case_").append(key).append(":\n");
-      y.append("    ldc ").append(key).append("\n");
-      y.append("    goto return_\n");
-    }
-
-    clazz.addStaticMethod("test", ImmutableList.of("I"), "I",
-        "    .limit stack 1",
-        "    .limit locals 1",
-        "    iload_0",
-        "    lookupswitch",
-        x.toString(),
-        "      default : case_default",
-        y.toString(),
-        "  case_default:",
-        "    ldc " + defaultValue,
-        "  return_:",
-        "    ireturn");
-
-    // Add the Jasmin class and a class from Java source with the main method.
-    AndroidApp.Builder appBuilder = AndroidApp.builder();
-    appBuilder.addClassProgramData(builder.buildClasses());
-    appBuilder.addProgramFiles(FilteredClassPath
-        .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
-    AndroidApp app = compileWithR8(appBuilder.build());
-
-    DexInspector inspector = new DexInspector(app);
-    MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
-    DexCode code = method.getMethod().getCode().asDexCode();
-
-    int packedSwitches = 0;
-    int sparseSwitches = 0;
-    int ifs = 0;
-    for (Instruction instruction : code.instructions) {
-      if (instruction instanceof PackedSwitch) {
-        packedSwitches++;
-      }
-      if (instruction instanceof SparseSwitch) {
-        sparseSwitches++;
-      }
-      if (instruction instanceof IfEq || instruction instanceof IfEqz) {
-        ifs++;
-      }
-    }
-
-    assertEquals(expectedPackedSwitches, packedSwitches);
-    assertEquals(expectedSparceSwitches, sparseSwitches);
-    assertEquals(expectedIfs, ifs);
-
-    // Run the code
-    List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList());
-    args.add(Integer.toString(defaultValue));
-    runOnArt(app, CheckSwitchInTestClass.class, args);
-  }
-
-  @Test
-  public void convertCasesToIf() throws Exception {
-    // Switches that are completely converted to ifs.
-    runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0);
-
-    // Switches that are completely converted to ifs and one switch.
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0);
-
-    // Switches that are completely converted to ifs and two switches.
-    runConvertCasesToIf(ImmutableList.of(
-        0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0);
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0);
-
-    // Switches that are completely converted two switches (one sparse and one packed).
-    runConvertCasesToIf(ImmutableList.of(
-        -1000, -900, -800, -700, -600, -500, -400, -300,
-        1000, 1001, 1002, 1003, 1004,
-        2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1);
-  }
-}
\ No newline at end of file