Version 1.3.53

Cherry-pick: Avoid using 'R' as a renamed class name.
CL: https://r8-review.googlesource.com/c/r8/+/33240

along with lots of test infra updates.

Also, update AvoidRTest a bit since 1.3 branch still has a default keep
rule behavior.

Bug: 123092153, 122937067
Change-Id: I64e448a4561e7e073d70e1132a8fe5f13cf67266
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index ff38d1b..58aecf0 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -78,6 +78,7 @@
 
     private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
     private Consumer<ProguardConfiguration.Builder> proguardConfigurationConsumer = null;
+    private Consumer<List<ProguardConfigurationRule>> syntheticProguardRulesConsumer = null;
     private final List<ProguardConfigurationSource> proguardConfigs = new ArrayList<>();
     private boolean disableTreeShaking = false;
     private boolean disableMinification = false;
@@ -463,6 +464,15 @@
           };
     }
 
+    void addSyntheticProguardRulesConsumerForTesting(
+        Consumer<List<ProguardConfigurationRule>> consumer) {
+      syntheticProguardRulesConsumer =
+          syntheticProguardRulesConsumer == null
+              ? consumer
+              : syntheticProguardRulesConsumer.andThen(consumer);
+
+    }
+
     // Internal for-testing method to add post-processors of the proguard configuration.
     void allowPartiallyImplementedProguardOptions() {
       allowPartiallyImplementedProguardOptions = true;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6438e9b..2cedaf5 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.52";
+  public static final String LABEL = "1.3.53";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ecf7158..f74009e 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -455,6 +455,11 @@
           .toCharArray();
       this.packageDictionaryIterator = packageDictionary.iterator();
       this.classDictionaryIterator = classDictionary.iterator();
+
+      // R.class in Android, which contains constant IDs to assets, can be bundled at any time.
+      // Insert `R` immediately so that the class name minifier can skip that name by default.
+      StringBuilder rBuilder = new StringBuilder().append(packagePrefix).append("R;");
+      usedTypeNames.add(appInfo.dexItemFactory.createString(rBuilder.toString()));
     }
 
     public String getPackageName() {
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 ec157e9..2f351ed 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -46,6 +46,11 @@
   }
 
   public final DexItemFactory itemFactory;
+
+  public ProguardConfiguration getProguardConfiguration() {
+    return proguardConfiguration;
+  }
+
   public final ProguardConfiguration proguardConfiguration;
   public final Reporter reporter;
 
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 178d351..21d7af9 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -7,18 +7,23 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
-public class D8TestCompileResult extends TestCompileResult<D8TestRunResult> {
+public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
   D8TestCompileResult(TestState state, AndroidApp app) {
     super(state, app);
   }
 
   @Override
+  public D8TestCompileResult self() {
+    return this;
+  }
+
+  @Override
   public Backend getBackend() {
     return Backend.DEX;
   }
 
   @Override
-  public D8TestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+  public D8TestRunResult createRunResult(ProcessResult result) {
     return new D8TestRunResult(app, result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index d14f12d..1aa095a 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -7,9 +7,14 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
-public class D8TestRunResult extends TestRunResult {
+public class D8TestRunResult extends TestRunResult<D8TestRunResult> {
 
   public D8TestRunResult(AndroidApp app, ProcessResult result) {
     super(app, result);
   }
+
+  @Override
+  protected D8TestRunResult self() {
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index fa1f314..2de0a67 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -7,19 +7,24 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
-public class DXTestCompileResult extends TestCompileResult<DXTestRunResult> {
+public class DXTestCompileResult extends TestCompileResult<DXTestCompileResult, DXTestRunResult> {
 
   DXTestCompileResult(TestState state, AndroidApp app) {
     super(state, app);
   }
 
   @Override
+  public DXTestCompileResult self() {
+    return this;
+  }
+
+  @Override
   public Backend getBackend() {
     return Backend.DEX;
   }
 
   @Override
-  public DXTestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+  public DXTestRunResult createRunResult(ProcessResult result) {
     return new DXTestRunResult(app, result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/DXTestRunResult.java b/src/test/java/com/android/tools/r8/DXTestRunResult.java
index c1df193..830cbd8 100644
--- a/src/test/java/com/android/tools/r8/DXTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestRunResult.java
@@ -7,9 +7,14 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 
-public class DXTestRunResult extends TestRunResult {
+public class DXTestRunResult extends TestRunResult<DXTestRunResult> {
 
   public DXTestRunResult(AndroidApp app, ProcessResult result) {
     super(app, result);
   }
+
+  @Override
+  protected DXTestRunResult self() {
+    return this;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 4e0bedf..9c9a977 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
@@ -24,7 +25,7 @@
 import java.util.List;
 import java.util.Set;
 
-public class JvmTestBuilder extends TestBuilder<JvmTestBuilder> {
+public class JvmTestBuilder extends TestBuilder<JvmTestRunResult, JvmTestBuilder> {
 
   private static class ClassFileResource implements ProgramResource {
 
@@ -107,9 +108,9 @@
   }
 
   @Override
-  public TestRunResult run(String mainClass) throws IOException {
+  public JvmTestRunResult run(String mainClass) throws IOException {
     ProcessResult result = ToolHelper.runJava(classpath, mainClass);
-    return new TestRunResult(builder.build(), result);
+    return new JvmTestRunResult(builder.build(), result);
   }
 
   @Override
@@ -124,20 +125,7 @@
 
   @Override
   public JvmTestBuilder addProgramClasses(Collection<Class<?>> classes) {
-    // Adding a collection of classes will build a jar of exactly those classes so that no other
-    // classes are made available via a too broad classpath directory.
-    List<ProgramResource> resources = ListUtils.map(classes, ClassFileResource::new);
-    AndroidApp build = AndroidApp.builder()
-        .addProgramResourceProvider(new ClassFileResourceProvider(resources)).build();
-    Path out;
-    try {
-      out = getState().getNewTempFolder().resolve("out.zip");
-      build.writeToZip(out, OutputMode.ClassFile);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-    classpath.add(out);
-    builder.addProgramFiles(out);
+    addProgramResources(ListUtils.map(classes, ClassFileResource::new));
     return self();
   }
 
@@ -147,6 +135,18 @@
         "No support for adding paths directly (we need to compute the descriptor)");
   }
 
+  @Override
+  public JvmTestBuilder addProgramClassFileData(Collection<byte[]> files) {
+    addProgramResources(
+        ListUtils.map(files, data ->
+            ProgramResource.fromBytes(
+                Origin.unknown(),
+                Kind.CF,
+                data,
+                Collections.singleton(TestBase.extractClassDescriptor(data)))));
+    return self();
+  }
+
   public JvmTestBuilder addClasspath(Path... paths) {
     return addClasspath(Arrays.asList(paths));
   }
@@ -162,4 +162,22 @@
   public JvmTestBuilder addTestClasspath() {
     return addClasspath(ToolHelper.getClassPathForTests());
   }
+
+  // Adding a collection of resources will build a jar of exactly those classes so that no other
+  // classes are made available via a too broad classpath directory.
+  private void addProgramResources(List<ProgramResource> resources) {
+    AndroidApp build =
+        AndroidApp.builder()
+            .addProgramResourceProvider(new ClassFileResourceProvider(resources))
+            .build();
+    Path out;
+    try {
+      out = getState().getNewTempFolder().resolve("out.zip");
+      build.writeToZip(out, OutputMode.ClassFile);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    classpath.add(out);
+    builder.addProgramFiles(out);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/JvmTestRunResult.java b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
new file mode 100644
index 0000000..b7a71fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JvmTestRunResult.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, 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;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class JvmTestRunResult extends TestRunResult<JvmTestRunResult> {
+
+  public JvmTestRunResult(AndroidApp app, ProcessResult result) {
+    super(app, result);
+  }
+
+  @Override
+  protected JvmTestRunResult self() {
+    return this;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 28ce1e6..0841770 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -6,9 +6,11 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -98,9 +100,16 @@
 
   // Ordered list of injar entries.
   private List<Path> injars = new ArrayList<>();
+
+  // Ordered list of libraryjar entries.
+  private List<Path> libraryjars = new ArrayList<>();
+
   // Proguard configuration file lines.
   private List<String> config = new ArrayList<>();
 
+  // Additional Proguard configuration files.
+  private List<Path> proguardConfigFiles = new ArrayList<>();
+
   private ProguardTestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
   }
@@ -132,26 +141,40 @@
         command.add("-injars");
         command.add(injar.toString());
       }
-      command.add("-libraryjars");
-      // TODO(sgjesse): Add support for running with Android Jar.
-      // command.add(ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
-      command.add(ToolHelper.getJava8RuntimeJar().toString());
+      for (Path libraryjar : libraryjars) {
+        command.add("-libraryjars");
+        command.add(libraryjar.toString());
+      }
+      if (libraryjars.isEmpty()) {
+        command.add("-libraryjars");
+        // TODO(sgjesse): Add support for running with Android Jar.
+        // command.add(ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
+        command.add(ToolHelper.getJava8RuntimeJar().toString());
+      }
       command.add("-include");
       command.add(configFile.toString());
+      for (Path proguardConfigFile : proguardConfigFiles) {
+        command.add("-include");
+        command.add(proguardConfigFile.toAbsolutePath().toString());
+      }
       command.add("-outjar");
       command.add(outJar.toString());
       command.add("-printmapping");
       command.add(mapFile.toString());
+      if (!enableTreeShaking) {
+        command.add("-dontshrink");
+      }
+      if (!enableMinification) {
+        command.add("-dontobfuscate");
+      }
       ProcessBuilder pbuilder = new ProcessBuilder(command);
       ProcessResult result = ToolHelper.runProcess(pbuilder);
       if (result.exitCode != 0) {
         throw new CompilationFailedException(result.toString());
       }
-      AndroidApp.Builder aaabuilder = AndroidApp.builder();
-      aaabuilder.addProgramFiles(outJar);
       String proguardMap =
           Files.exists(mapFile) ? FileUtils.readTextFile(mapFile, Charsets.UTF_8) : "";
-      return new ProguardTestCompileResult(getState(), aaabuilder.build(), proguardMap);
+      return new ProguardTestCompileResult(getState(), outJar, proguardMap);
     } catch (IOException e) {
       throw new CompilationFailedException(e);
     }
@@ -179,8 +202,27 @@
 
   @Override
   public ProguardTestBuilder addProgramFiles(Collection<Path> files) {
+    for (Path file : files) {
+      if (FileUtils.isJarFile(file)) {
+        injars.add(file);
+      } else {
+        throw new Unimplemented(
+            "No support for adding class files directly (we need to compute the descriptor)");
+      }
+    }
+    return self();
+  }
+
+  @Override
+  public ProguardTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
     throw new Unimplemented(
-        "No support for adding paths directly (we need to compute the descriptor)");
+        "No support for adding class files directly (we need to compute the descriptor)");
+  }
+
+  @Override
+  public ProguardTestBuilder addKeepRuleFiles(List<Path> proguardConfigFiles) {
+    this.proguardConfigFiles.addAll(proguardConfigFiles);
+    return self();
   }
 
   @Override
@@ -188,4 +230,51 @@
     config.addAll(rules);
     return self();
   }
+  @Override
+  public ProguardTestBuilder addLibraryFiles(Collection<Path> files) {
+    for (Path file : files) {
+      if (FileUtils.isJarFile(file)) {
+        libraryjars.add(file);
+      } else {
+        throw new Unimplemented(
+            "No support for adding class files directly (we need to compute the descriptor)");
+      }
+    }
+    return self();
+  }
+
+  @Override
+  public ProguardTestBuilder setProgramConsumer(ProgramConsumer programConsumer) {
+    throw new Unimplemented("No support for program consumer");
+  }
+
+  @Override
+  public ProguardTestBuilder setMinApi(AndroidApiLevel minApiLevel) {
+    throw new Unimplemented("No support for setting min api");
+  }
+
+  @Override
+  public ProguardTestBuilder addMainDexListFiles(Collection<Path> files) {
+    throw new Unimplemented("No support for adding main dex list files");
+  }
+
+  @Override
+  public ProguardTestBuilder setMode(CompilationMode mode) {
+    throw new Unimplemented("No support for setting compilation mode");
+  }
+
+  @Override
+  public ProguardTestBuilder noDesugaring() {
+    throw new Unimplemented("No support for disabling desugaring");
+  }
+
+  @Override
+  public DebugTestConfig debugConfig() {
+    throw new Unimplemented("No support for debug config");
+  }
+
+  @Override
+  public ProguardTestBuilder addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
+    throw new Unimplemented("No support for changing internal options");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index bef1e02..34144c7 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -8,17 +8,30 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.util.concurrent.ExecutionException;
 
-public class ProguardTestCompileResult extends TestCompileResult<ProguardTestRunResult> {
+public class ProguardTestCompileResult
+    extends TestCompileResult<ProguardTestCompileResult, ProguardTestRunResult> {
 
+  private final Path outputJar;
   private final String proguardMap;
 
-  ProguardTestCompileResult(TestState state, AndroidApp app, String proguardMap) {
-    super(state, app);
+  ProguardTestCompileResult(TestState state, Path outputJar, String proguardMap) {
+    super(state, AndroidApp.builder().addProgramFiles(outputJar).build());
+    this.outputJar = outputJar;
     this.proguardMap = proguardMap;
   }
 
+  public Path outputJar() {
+    return outputJar;
+  }
+
+  @Override
+  public ProguardTestCompileResult self() {
+    return this;
+  }
+
   @Override
   public Backend getBackend() {
     return Backend.CF;
@@ -30,7 +43,7 @@
   }
 
   @Override
-  public ProguardTestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+  public ProguardTestRunResult createRunResult(ProcessResult result) {
     return new ProguardTestRunResult(app, result, proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
index 9e0d875..e242a38 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestRunResult.java
@@ -12,7 +12,7 @@
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 
-public class ProguardTestRunResult extends TestRunResult {
+public class ProguardTestRunResult extends TestRunResult<ProguardTestRunResult> {
 
   private final String proguardMap;
 
@@ -22,6 +22,11 @@
   }
 
   @Override
+  protected ProguardTestRunResult self() {
+    return this;
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     // See comment in base class.
     assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 5550415..3be4e00 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -7,8 +7,11 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.TestBase.R8Mode;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -32,6 +35,7 @@
   private boolean enableInliningAnnotations = false;
   private boolean enableClassInliningAnnotations = false;
   private boolean enableMergeAnnotations = false;
+  private List<String> keepRules = new ArrayList<>();
 
   @Override
   R8TestBuilder self() {
@@ -45,10 +49,32 @@
     if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
+    if (!keepRules.isEmpty()) {
+      builder.addProguardConfiguration(keepRules, Origin.unknown());
+    }
     StringBuilder proguardMapBuilder = new StringBuilder();
+    builder.setDisableTreeShaking(!enableTreeShaking);
+    builder.setDisableMinification(!enableMinification);
     builder.setProguardMapConsumer((string, ignore) -> proguardMapBuilder.append(string));
-    ToolHelper.runR8WithoutResult(builder.build(), optionsConsumer);
-    return new R8TestCompileResult(getState(), backend, app.get(), proguardMapBuilder.toString());
+
+    class Box {
+      private List<ProguardConfigurationRule> syntheticProguardRules;
+      private ProguardConfiguration proguardConfiguration;
+    }
+    Box box = new Box();
+    ToolHelper.addSyntheticProguardRulesConsumerForTesting(
+        builder, rules -> box.syntheticProguardRules = rules);
+    ToolHelper.runR8WithoutResult(
+        builder.build(),
+        optionsConsumer.andThen(
+            options -> box.proguardConfiguration = options.getProguardConfiguration()));
+    return new R8TestCompileResult(
+        getState(),
+        backend,
+        app.get(),
+        box.proguardConfiguration,
+        box.syntheticProguardRules,
+        proguardMapBuilder.toString());
   }
 
   public R8TestBuilder addDataResources(List<DataEntryResource> resources) {
@@ -57,8 +83,16 @@
   }
 
   @Override
+  public R8TestBuilder addKeepRuleFiles(List<Path> files) {
+    builder.addProguardConfigurationFiles(files);
+    return self();
+  }
+
+  @Override
   public R8TestBuilder addKeepRules(Collection<String> rules) {
-    builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
+    // Delay adding the actual rules so that we only associate a single origin and unique lines to
+    // each actual rule.
+    keepRules.addAll(rules);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 35cb176..149b5df 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -5,23 +5,42 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
-public class R8TestCompileResult extends TestCompileResult<R8TestRunResult> {
+public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
 
   private final Backend backend;
+  private final ProguardConfiguration proguardConfiguration;
+  private final List<ProguardConfigurationRule> syntheticProguardRules;
   private final String proguardMap;
 
-  R8TestCompileResult(TestState state, Backend backend, AndroidApp app, String proguardMap) {
+  R8TestCompileResult(
+      TestState state,
+      Backend backend,
+      AndroidApp app,
+      ProguardConfiguration proguardConfiguration,
+      List<ProguardConfigurationRule> syntheticProguardRules,
+      String proguardMap) {
     super(state, app);
     this.backend = backend;
+    this.proguardConfiguration = proguardConfiguration;
+    this.syntheticProguardRules = syntheticProguardRules;
     this.proguardMap = proguardMap;
   }
 
   @Override
+  public R8TestCompileResult self() {
+    return this;
+  }
+
+  @Override
   public Backend getBackend() {
     return backend;
   }
@@ -31,8 +50,28 @@
     return new CodeInspector(app, proguardMap);
   }
 
+  public ProguardConfiguration getProguardConfiguration() {
+    return proguardConfiguration;
+  }
+
+  public R8TestCompileResult inspectProguardConfiguration(
+      Consumer<ProguardConfiguration> consumer) {
+    consumer.accept(getProguardConfiguration());
+    return self();
+  }
+
+  public List<ProguardConfigurationRule> getSyntheticProguardRules() {
+    return syntheticProguardRules;
+  }
+
+  public R8TestCompileResult inspectSyntheticProguardRules(
+      Consumer<List<ProguardConfigurationRule>> consumer) {
+    consumer.accept(getSyntheticProguardRules());
+    return self();
+  }
+
   @Override
-  public R8TestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+  public R8TestRunResult createRunResult(ProcessResult result) {
     return new R8TestRunResult(app, result, proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 2c42b95..2203c84 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -12,20 +12,32 @@
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 
-public class R8TestRunResult extends TestRunResult {
+public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
 
   private final String proguardMap;
 
-  public R8TestRunResult(AndroidApp app, ProcessResult result, String proguardMap) {
+  public R8TestRunResult(
+      AndroidApp app,
+      ProcessResult result,
+      String proguardMap) {
     super(app, result);
     this.proguardMap = proguardMap;
   }
 
   @Override
+  protected R8TestRunResult self() {
+    return this;
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     // See comment in base class.
     assertSuccess();
     assertNotNull(app);
     return new CodeInspector(app, proguardMap);
   }
+
+  public String proguardMap() {
+    return proguardMap;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a839c6f..a95ccfc 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -772,7 +772,15 @@
     }
   }
 
-  private String extractClassName(byte[] ccc) {
+  public static String extractClassName(byte[] ccc) {
+    return DescriptorUtils.descriptorToJavaType(extractClassDescriptor(ccc));
+  }
+
+  public static String extractClassDescriptor(byte[] ccc) {
+    return "L" + extractClassInternalType(ccc) + ";";
+  }
+
+  private static String extractClassInternalType(byte[] ccc) {
     class ClassNameExtractor extends ClassVisitor {
       private String className;
 
@@ -788,13 +796,10 @@
           String signature,
           String superName,
           String[] interfaces) {
-        className =
-            name.replace(
-                DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR,
-                DescriptorUtils.JAVA_PACKAGE_SEPARATOR);
+        className = name;
       }
 
-      String getClassName() {
+      String getClassInternalType() {
         return className;
       }
     }
@@ -803,7 +808,7 @@
     ClassNameExtractor extractor = new ClassNameExtractor();
     reader.accept(
         extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
-    return extractor.getClassName();
+    return extractor.getClassInternalType();
   }
 
   protected Path writeToJar(List<byte[]> classes) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
new file mode 100644
index 0000000..5fcd1b5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, 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;
+
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Collection;
+
+public abstract class TestBaseBuilder<
+        C extends BaseCommand,
+        B extends BaseCommand.Builder<C, B>,
+        CR extends TestBaseResult<CR, RR>,
+        RR extends TestRunResult,
+        T extends TestBaseBuilder<C, B, CR, RR, T>>
+    extends TestBuilder<RR, T> {
+
+  final B builder;
+
+  TestBaseBuilder(TestState state, B builder) {
+    super(state);
+    this.builder = builder;
+  }
+
+  @Override
+  public T addProgramClassFileData(Collection<byte[]> classes) {
+    for (byte[] clazz : classes) {
+      builder.addClassProgramData(clazz, Origin.unknown());
+    }
+    return self();
+  }
+
+  @Override
+  public T addProgramFiles(Collection<Path> files) {
+    builder.addProgramFiles(files);
+    return self();
+  }
+
+  @Override
+  public T addLibraryFiles(Collection<Path> files) {
+    builder.addLibraryFiles(files);
+    return self();
+  }
+
+  public T addMainDexListFiles(Collection<Path> files) {
+    builder.addMainDexListFiles(files);
+    return self();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBaseResult.java b/src/test/java/com/android/tools/r8/TestBaseResult.java
new file mode 100644
index 0000000..e7623a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBaseResult.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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;
+
+public abstract class TestBaseResult<CR extends TestBaseResult<CR, RR>, RR extends TestRunResult> {
+  final TestState state;
+
+  TestBaseResult(TestState state) {
+    this.state = state;
+  }
+
+  public abstract CR self();
+}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 4977d94..c67c05c 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -3,18 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.utils.ListUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
 
-public abstract class TestBuilder<T extends TestBuilder<T>> {
+public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
 
   private final TestState state;
 
@@ -28,10 +24,10 @@
 
   abstract T self();
 
-  public abstract TestRunResult run(String mainClass)
+  public abstract RR run(String mainClass)
       throws IOException, CompilationFailedException;
 
-  public TestRunResult run(Class mainClass) throws IOException, CompilationFailedException {
+  public RR run(Class mainClass) throws IOException, CompilationFailedException {
     return run(mainClass.getTypeName());
   }
 
@@ -39,6 +35,12 @@
 
   public abstract T addProgramFiles(Collection<Path> files);
 
+  public abstract T addProgramClassFileData(Collection<byte[]> classes);
+
+  public T addProgramClassFileData(byte[]... classes) {
+    return addProgramClassFileData(Arrays.asList(classes));
+  }
+
   public T addProgramClasses(Class<?>... classes) {
     return addProgramClasses(Arrays.asList(classes));
   }
@@ -86,14 +88,6 @@
   }
 
   static Collection<Path> getFilesForInnerClasses(Collection<Class<?>> classes) throws IOException {
-    Set<Path> paths = new HashSet<>();
-    for (Class clazz : classes) {
-      Path path = ToolHelper.getClassFileForTestClass(clazz);
-      String prefix = path.toString().replace(CLASS_EXTENSION, "$");
-      paths.addAll(
-          ToolHelper.getClassFilesForTestDirectory(
-              path.getParent(), p -> p.toString().startsWith(prefix)));
-    }
-    return paths;
+    return ToolHelper.getClassFilesForInnerClasses(classes);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 12d1990..acc76bf 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -11,25 +11,32 @@
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
-public abstract class TestCompileResult<RR extends TestRunResult> {
-  final TestState state;
+public abstract class TestCompileResult<
+        CR extends TestCompileResult<CR, RR>, RR extends TestRunResult>
+    extends TestBaseResult<CR, RR> {
+
   public final AndroidApp app;
+  final List<Path> additionalRunClassPath = new ArrayList<>();
 
   TestCompileResult(TestState state, AndroidApp app) {
-    this.state = state;
+    super(state);
     this.app = app;
   }
 
   public abstract Backend getBackend();
 
-  protected abstract RR createRunResult(AndroidApp add, ProcessResult result);
+  protected abstract RR createRunResult(ProcessResult result);
 
   public RR run(Class<?> mainClass) throws IOException {
     return run(mainClass.getTypeName());
@@ -38,27 +45,31 @@
   public RR run(String mainClass) throws IOException {
     switch (getBackend()) {
       case DEX:
-        return runArt(mainClass);
+        return runArt(additionalRunClassPath, mainClass);
       case CF:
-        return runJava(mainClass);
+        return runJava(additionalRunClassPath, mainClass);
       default:
         throw new Unreachable();
     }
   }
 
-  public TestCompileResult writeToZip(Path file) throws IOException {
+  public CR addRunClasspath(List<Path> classpath) {
+    additionalRunClassPath.addAll(classpath);
+    return self();
+  }
+
+  public CR writeToZip(Path file) throws IOException {
     app.writeToZip(file, getBackend() == DEX ? OutputMode.DexIndexed : OutputMode.ClassFile);
-    return this;
+    return self();
   }
 
   public CodeInspector inspector() throws IOException, ExecutionException {
     return new CodeInspector(app);
   }
 
-  public TestCompileResult<RR> inspect(Consumer<CodeInspector> consumer)
-      throws IOException, ExecutionException {
+  public CR inspect(Consumer<CodeInspector> consumer) throws IOException, ExecutionException {
     consumer.accept(inspector());
-    return this;
+    return self();
   }
 
   public DebugTestConfig debugConfig() {
@@ -85,17 +96,25 @@
     }
   }
 
-  private RR runJava(String mainClass) throws IOException {
+  private RR runJava(List<Path> additionalClassPath, String mainClass) throws IOException {
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.ClassFile);
-    ProcessResult result = ToolHelper.runJava(out, mainClass);
-    return createRunResult(app, result);
+    List<Path> classPath = ImmutableList.<Path>builder()
+        .addAll(additionalClassPath)
+        .add(out)
+        .build();
+    ProcessResult result = ToolHelper.runJava(classPath, mainClass);
+    return createRunResult(result);
   }
 
-  private RR runArt(String mainClass) throws IOException {
+  private RR runArt(List<Path> additionalClassPath, String mainClass) throws IOException {
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.DexIndexed);
-    ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
-    return createRunResult(app, result);
+    List<String> classPath = ImmutableList.<String>builder()
+        .addAll(additionalClassPath.stream().map(Path::toString).collect(Collectors.toList()))
+        .add(out.toString())
+        .build();
+    ProcessResult result = ToolHelper.runArtRaw(classPath, mainClass, dummy -> {});
+    return createRunResult(result);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 810d292..b2b6047 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.base.Suppliers;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.function.Consumer;
@@ -19,10 +20,10 @@
 public abstract class TestCompilerBuilder<
         C extends BaseCompilerCommand,
         B extends BaseCompilerCommand.Builder<C, B>,
-        CR extends TestCompileResult<RR>,
+        CR extends TestCompileResult<CR, RR>,
         RR extends TestRunResult,
         T extends TestCompilerBuilder<C, B, CR, RR, T>>
-    extends TestBuilder<T> {
+    extends TestBaseBuilder<C, B, CR, RR, T> {
 
   public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
       new Consumer<InternalOptions>() {
@@ -30,7 +31,6 @@
         public void accept(InternalOptions options) {}
       };
 
-  final B builder;
   final Backend backend;
 
   // Default initialized setup. Can be overwritten if needed.
@@ -38,10 +38,10 @@
   private ProgramConsumer programConsumer;
   private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
   private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
+  private PrintStream stdout = null;
 
   TestCompilerBuilder(TestState state, B builder, Backend backend) {
-    super(state);
-    this.builder = builder;
+    super(state, builder);
     this.backend = backend;
     defaultLibrary = TestBase.runtimeJar(backend);
     programConsumer = TestBase.emptyConsumer(backend);
@@ -54,7 +54,9 @@
       throws CompilationFailedException;
 
   public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
-    this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
+    if (optionsConsumer != null) {
+      this.optionsConsumer = this.optionsConsumer.andThen(optionsConsumer);
+    }
     return self();
   }
 
@@ -67,11 +69,21 @@
     if (backend == Backend.DEX && defaultMinApiLevel != null) {
       builder.setMinApiLevel(defaultMinApiLevel.getLevel());
     }
-    return internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
+    PrintStream oldOut = System.out;
+    try {
+      if (stdout != null) {
+        System.setOut(stdout);
+      }
+      return internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
+    } finally {
+      if (stdout != null) {
+        System.setOut(oldOut);
+      }
+    }
   }
 
   @Override
-  public TestRunResult run(String mainClass) throws IOException, CompilationFailedException {
+  public RR run(String mainClass) throws IOException, CompilationFailedException {
     return compile().run(mainClass);
   }
 
@@ -113,15 +125,19 @@
   }
 
   @Override
-  public T addProgramFiles(Collection<Path> files) {
-    builder.addProgramFiles(files);
+  public T addLibraryFiles(Collection<Path> files) {
+    defaultLibrary = null;
+    return super.addLibraryFiles(files);
+  }
+
+  public T noDesugaring() {
+    builder.setDisableDesugaring(true);
     return self();
   }
 
-  @Override
-  public T addLibraryFiles(Collection<Path> files) {
-    defaultLibrary = null;
-    builder.addLibraryFiles(files);
+  public T redirectStdOut(PrintStream printStream) {
+    assert stdout == null;
+    stdout = printStream;
     return self();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 46dc4cc..07390b7 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -15,9 +15,10 @@
 import java.io.IOException;
 import java.io.PrintStream;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
 import org.hamcrest.Matcher;
 
-public class TestRunResult {
+public abstract class TestRunResult<RR extends TestRunResult<?>> {
   protected final AndroidApp app;
   private final ProcessResult result;
 
@@ -26,33 +27,55 @@
     this.result = result;
   }
 
-  public TestRunResult assertSuccess() {
+  abstract RR self();
+
+  public AndroidApp app() {
+    return app;
+  }
+
+  public String getStdOut() {
+    return result.stdout;
+  }
+
+  public String getStdErr() {
+    return result.stderr;
+  }
+
+  public int getExitCode() {
+    return result.exitCode;
+  }
+
+  public RR assertSuccess() {
     assertEquals(errorMessage("Expected run to succeed."), 0, result.exitCode);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertFailure() {
+  public RR assertFailure() {
     assertNotEquals(errorMessage("Expected run to fail."), 0, result.exitCode);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertFailureWithOutput(String expected) {
+  public RR assertFailureWithOutput(String expected) {
     assertFailure();
     assertEquals(errorMessage("Run stdout incorrect.", expected), expected, result.stdout);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertFailureWithErrorThatMatches(Matcher<String> matcher) {
+  public RR assertFailureWithErrorThatMatches(Matcher<String> matcher) {
     assertFailure();
     assertThat(
         errorMessage("Run stderr incorrect.", matcher.toString()), result.stderr, matcher);
-    return this;
+    return self();
   }
 
-  public TestRunResult assertSuccessWithOutput(String expected) {
+  public RR assertSuccessWithOutput(String expected) {
     assertSuccess();
     assertEquals(errorMessage("Run stdout incorrect.", expected), expected, result.stdout);
-    return this;
+    return self();
+  }
+
+  public <R> R map(Function<RR, R> mapper) {
+    return mapper.apply(self());
   }
 
   public CodeInspector inspector() throws IOException, ExecutionException {
@@ -63,17 +86,24 @@
     return new CodeInspector(app);
   }
 
-  public TestRunResult inspect(Consumer<CodeInspector> consumer)
+  public RR inspect(Consumer<CodeInspector> consumer)
       throws IOException, ExecutionException {
     consumer.accept(inspector());
-    return this;
+    return self();
   }
 
-  private String errorMessage(String message) {
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    appendInfo(builder);
+    return builder.toString();
+  }
+
+  String errorMessage(String message) {
     return errorMessage(message, null);
   }
 
-  private String errorMessage(String message, String expected) {
+  String errorMessage(String message, String expected) {
     StringBuilder builder = new StringBuilder(message).append('\n');
     if (expected != null) {
       if (expected.contains(System.lineSeparator())) {
@@ -102,24 +132,24 @@
     builder.append("COMMAND: ").append(result.command).append('\n').append(result);
   }
 
-  public TestRunResult writeInfo(PrintStream ps) {
+  public RR writeInfo(PrintStream ps) {
     StringBuilder sb = new StringBuilder();
     appendInfo(sb);
     ps.println(sb.toString());
-    return this;
+    return self();
   }
 
-  public TestRunResult writeApplicaion(PrintStream ps) {
+  public RR writeApplicaion(PrintStream ps) {
     StringBuilder sb = new StringBuilder();
     appendApplication(sb);
     ps.println(sb.toString());
-    return this;
+    return self();
   }
 
-  public TestRunResult writeProcessResult(PrintStream ps) {
+  public RR writeProcessResult(PrintStream ps) {
     StringBuilder sb = new StringBuilder();
     appendProcessResult(sb);
     ps.println(sb.toString());
-    return this;
+    return self();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2704039..916e1a8 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -5,22 +5,51 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 public abstract class TestShrinkerBuilder<
         C extends BaseCompilerCommand,
         B extends BaseCompilerCommand.Builder<C, B>,
-        CR extends TestCompileResult<RR>,
+        CR extends TestCompileResult<CR, RR>,
         RR extends TestRunResult,
         T extends TestCompilerBuilder<C, B, CR, RR, T>>
     extends TestCompilerBuilder<C, B, CR, RR, T> {
 
+  protected boolean enableMinification = true;
+  protected boolean enableTreeShaking = true;
+
   TestShrinkerBuilder(TestState state, B builder, Backend backend) {
     super(state, builder, backend);
   }
 
+  public T treeShaking(boolean enable) {
+    enableTreeShaking = enable;
+    return self();
+  }
+
+  public T noTreeShaking() {
+    return treeShaking(false);
+  }
+
+  public T minification(boolean enable) {
+    enableMinification = enable;
+    return self();
+  }
+
+  public T noMinification() {
+    return minification(false);
+  }
+
+  public abstract T addKeepRuleFiles(List<Path> files);
+
+  public T addKeepRuleFiles(Path... files) throws IOException {
+    return addKeepRuleFiles(Arrays.asList(files));
+  }
+
   public abstract T addKeepRules(Collection<String> rules);
 
   public T addKeepRules(String... rules) {
@@ -38,6 +67,13 @@
     return self();
   }
 
+  public T addKeepClassAndMembersRules(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addKeepRules("-keep class " + clazz.getTypeName() + " { *; }");
+    }
+    return self();
+  }
+
   public T addKeepPackageRules(Package pkg) {
     return addKeepRules("-keep class " + pkg.getName() + ".*");
   }
@@ -48,9 +84,6 @@
 
   public T addKeepMainRule(String mainClass) {
     return addKeepRules(
-        StringUtils.joinLines(
-            "-keep class " + mainClass + " {",
-            "  public static void main(java.lang.String[]);",
-            "}"));
+        "-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index c1ba2c5..b41899f 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -56,6 +58,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -748,6 +751,19 @@
         Paths.get("", parts.toArray(new String[parts.size() - 1])));
   }
 
+  public static Collection<Path> getClassFilesForInnerClasses(Collection<Class<?>> classes)
+      throws IOException {
+    Set<Path> paths = new HashSet<>();
+    for (Class clazz : classes) {
+      Path path = ToolHelper.getClassFileForTestClass(clazz);
+      String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+      paths.addAll(
+          ToolHelper.getClassFilesForTestDirectory(
+              path.getParent(), p -> p.toString().startsWith(prefix)));
+    }
+    return paths;
+  }
+
   public static String getJarEntryForTestPackage(Package pkg) {
     List<String> parts = getNamePartsForTestPackage(pkg);
     return String.join("/", parts);
@@ -1607,6 +1623,12 @@
     return builder;
   }
 
+  public static R8Command.Builder addSyntheticProguardRulesConsumerForTesting(
+      R8Command.Builder builder, Consumer<List<ProguardConfigurationRule>> consumer) {
+    builder.addSyntheticProguardRulesConsumerForTesting(consumer);
+    return builder;
+  }
+
   public static R8Command.Builder allowPartiallyImplementedProguardOptions(
       R8Command.Builder builder) {
     builder.allowPartiallyImplementedProguardOptions();
diff --git a/src/test/java/com/android/tools/r8/naming/AvoidRTest.java b/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
new file mode 100644
index 0000000..e1d073c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AvoidRTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2019, 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.naming;
+
+import static com.android.tools.r8.utils.DescriptorUtils.getSimpleClassNameFromDescriptor;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AvoidRTest extends JasminTestBase {
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public AvoidRTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test_withObfuscationDictionary() throws Exception {
+    Path dictionary = temp.newFile("dictionary.txt").toPath();
+    FileUtils.writeTextFile(dictionary, StringUtils.lines("P", "Q", "R", "S", "T"));
+    Set<String> expectedNames = ImmutableSet.of("P", "Q", "S", "T");
+
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    R8TestBuilder builder = testForR8(backend);
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("TopLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("p1/SecondLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("p1/p2/ThirdLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 4; i++) {
+      jasminBuilder.addClass("p2/SecondLevel" + Integer.toString(i));
+    }
+    builder.addKeepRules("-keep,allowobfuscation class *");
+    builder.addProgramClassFileData(jasminBuilder.buildClasses());
+    Set<String> usedDescriptors = new HashSet<>();
+    builder.noTreeShaking()
+        .addKeepRules("-classobfuscationdictionary " + dictionary)
+        .compile()
+        .inspect(codeInspector -> {
+          codeInspector.forAllClasses(classSubject -> {
+            assertThat(classSubject, isRenamed());
+            String renamedDescriptor = classSubject.getFinalDescriptor();
+            assertTrue(usedDescriptors.add(renamedDescriptor));
+            assertNotEquals("R", getSimpleClassNameFromDescriptor(renamedDescriptor));
+            assertTrue(expectedNames.contains(getSimpleClassNameFromDescriptor(renamedDescriptor)));
+          });
+        });
+  }
+
+  @Test
+  public void test_withoutPackageHierarchy() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    R8TestBuilder builder = testForR8(backend);
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("TestClass" + Integer.toString(i));
+    }
+    builder.addKeepRules("-keep,allowobfuscation class *");
+    builder.addProgramClassFileData(jasminBuilder.buildClasses());
+    Set<String> usedNames = new HashSet<>();
+    builder.noTreeShaking()
+        .compile()
+        .inspect(codeInspector -> {
+          codeInspector.forAllClasses(classSubject -> {
+            assertThat(classSubject, isRenamed());
+            assertTrue(usedNames.add(classSubject.getFinalName()));
+            assertNotEquals("R", classSubject.getFinalName());
+          });
+        });
+    assertTrue(usedNames.contains("Q"));
+    assertTrue(usedNames.contains("S"));
+  }
+
+  private void test_withPackageHierarchy(String keepRule) throws Exception {
+    R8TestBuilder builder = testForR8(backend);
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("TopLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("p1/SecondLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("p1/p2/ThirdLevel" + Integer.toString(i));
+    }
+    for (int i = 0; i < 26 * 2; i++) {
+      jasminBuilder.addClass("p2/SecondLevel" + Integer.toString(i));
+    }
+    builder.addKeepRules("-keep,allowobfuscation class *");
+    builder.addProgramClassFileData(jasminBuilder.buildClasses());
+    Set<String> usedDescriptors = new HashSet<>();
+    builder.noTreeShaking()
+        .addKeepRules(keepRule)
+        .compile()
+        .inspect(codeInspector -> {
+          codeInspector.forAllClasses(classSubject -> {
+            assertThat(classSubject, isRenamed());
+            String renamedDescriptor = classSubject.getFinalDescriptor();
+            assertTrue(usedDescriptors.add(renamedDescriptor));
+            assertNotEquals("R", getSimpleClassNameFromDescriptor(renamedDescriptor));
+          });
+        });
+  }
+
+  @Test
+  public void test_withPackageHierarchy_default() throws Exception {
+    test_withPackageHierarchy("");
+  }
+
+  @Test
+  public void test_withPackageHierarchy_repackage() throws Exception {
+    // Repackage every class to the top-level.
+    test_withPackageHierarchy("-repackageclasses");
+  }
+
+  @Test
+  public void test_withPackageHierarchy_flatten() throws Exception {
+    // Repackage every package to the top-level.
+    test_withPackageHierarchy("-flattenpackagehierarchy");
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
index e5f9799..ce9b84d 100644
--- a/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/fields/FieldsTestBase.java
@@ -6,12 +6,12 @@
 
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.Consumer;
 
 public abstract class FieldsTestBase extends TestBase {