Disallow diagnostic messages, stdout and stderr by default in tests

Bug: 122819270
Change-Id: I34dc74b33ed8e306dcfa51c0ee7c2bfa680e2442
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 117ca7b..80b23ac 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -24,6 +24,16 @@
   }
 
   @Override
+  public String getStdout() {
+    return state.getStdout();
+  }
+
+  @Override
+  public String getStderr() {
+    return state.getStderr();
+  }
+
+  @Override
   public D8TestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
     return new D8TestRunResult(app, runtime, result);
   }
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index 3cf9b73..e362f78 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 
 public class DXTestCompileResult extends TestCompileResult<DXTestCompileResult, DXTestRunResult> {
@@ -23,6 +24,16 @@
   }
 
   @Override
+  public String getStdout() {
+    throw new Unimplemented("Unexpected attempt to access stdout from dx");
+  }
+
+  @Override
+  public String getStderr() {
+    throw new Unimplemented("Unexpected attempt to access stderr from dx");
+  }
+
+  @Override
   public DXTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
     return new DXTestRunResult(app, runtime, result);
   }
diff --git a/src/test/java/com/android/tools/r8/DumpInputsTest.java b/src/test/java/com/android/tools/r8/DumpInputsTest.java
index e7f2ef8..84febed 100644
--- a/src/test/java/com/android/tools/r8/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/DumpInputsTest.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -24,8 +24,6 @@
 @RunWith(Parameterized.class)
 public class DumpInputsTest extends TestBase {
 
-  static final String EXPECTED = StringUtils.lines("Hello, world");
-
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
@@ -62,6 +60,9 @@
         .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
         .addKeepMainRule(TestClass.class)
         .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+        .allowDiagnosticInfoMessages()
+        .compile()
+        .assertAllInfoMessagesMatch(containsString("Dumped compilation inputs to:"))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines("Hello, world");
     assertTrue(Files.isDirectory(dumpDir));
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
index 04a37fa..75cd5526 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
@@ -58,6 +59,16 @@
   }
 
   @Override
+  public String getStdout() {
+    throw new Unimplemented("Unexpected attempt to access stdout from external R8");
+  }
+
+  @Override
+  public String getStderr() {
+    throw new Unimplemented("Unexpected attempt to access stderr from external R8");
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     return new CodeInspector(app, proguardMap);
   }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index fbb8282..dbb3c6e 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
@@ -37,6 +38,16 @@
   }
 
   @Override
+  public String getStdout() {
+    throw new Unimplemented("Unexpected attempt to access stdout from Proguard");
+  }
+
+  @Override
+  public String getStderr() {
+    throw new Unimplemented("Unexpected attempt to access stderr from Proguard");
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     return new CodeInspector(app, proguardMap);
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 1dca05c..bd27965 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
@@ -26,14 +27,23 @@
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import org.hamcrest.core.IsAnything;
 
 public abstract class R8TestBuilder<T extends R8TestBuilder<T>>
     extends TestShrinkerBuilder<R8Command, Builder, R8TestCompileResult, R8TestRunResult, T> {
 
+  enum AllowedDiagnosticMessages {
+    ALL,
+    INFO,
+    NONE,
+    WARNING
+  }
+
   R8TestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
   }
 
+  private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
   private boolean enableInliningAnnotations = false;
   private boolean enableNeverClassInliningAnnotations = false;
   private boolean enableMergeAnnotations = false;
@@ -105,14 +115,32 @@
         builder.build(),
         optionsConsumer.andThen(
             options -> box.proguardConfiguration = options.getProguardConfiguration()));
-    return new R8TestCompileResult(
-        getState(),
-        getOutputMode(),
-        app.get(),
-        box.proguardConfiguration,
-        box.syntheticProguardRules,
-        proguardMapBuilder.toString(),
-        graphConsumer);
+    R8TestCompileResult compileResult =
+        new R8TestCompileResult(
+            getState(),
+            getOutputMode(),
+            app.get(),
+            box.proguardConfiguration,
+            box.syntheticProguardRules,
+            proguardMapBuilder.toString(),
+            graphConsumer);
+    switch (allowedDiagnosticMessages) {
+      case ALL:
+        compileResult.assertDiagnosticMessageThatMatches(new IsAnything<>());
+        break;
+      case INFO:
+        compileResult.assertOnlyInfos();
+        break;
+      case NONE:
+        compileResult.assertNoMessages();
+        break;
+      case WARNING:
+        compileResult.assertOnlyWarnings();
+        break;
+      default:
+        throw new Unreachable();
+    }
+    return compileResult;
   }
 
   public Builder getBuilder() {
@@ -201,6 +229,36 @@
     return addOptionsModification(options -> options.testing.allowClassInlinerGracefulExit = true);
   }
 
+  public T allowDiagnosticMessages() {
+    assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
+    allowedDiagnosticMessages = AllowedDiagnosticMessages.ALL;
+    return self();
+  }
+
+  public T allowDiagnosticInfoMessages() {
+    return allowDiagnosticInfoMessages(true);
+  }
+
+  public T allowDiagnosticInfoMessages(boolean condition) {
+    if (condition) {
+      assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
+      allowedDiagnosticMessages = AllowedDiagnosticMessages.INFO;
+    }
+    return self();
+  }
+
+  public T allowDiagnosticWarningMessages() {
+    return allowDiagnosticWarningMessages(true);
+  }
+
+  public T allowDiagnosticWarningMessages(boolean condition) {
+    if (condition) {
+      assert allowedDiagnosticMessages == AllowedDiagnosticMessages.NONE;
+      allowedDiagnosticMessages = AllowedDiagnosticMessages.WARNING;
+    }
+    return self();
+  }
+
   public T allowUnusedProguardConfigurationRules() {
     return addOptionsModification(
         options -> options.testing.allowUnusedProguardConfigurationRules = true);
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index c4ec2aa..ae6fc69 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -53,6 +53,16 @@
   }
 
   @Override
+  public String getStdout() {
+    return state.getStdout();
+  }
+
+  @Override
+  public String getStderr() {
+    return state.getStderr();
+  }
+
+  @Override
   public CodeInspector inspector() throws IOException, ExecutionException {
     return new CodeInspector(app, proguardMap);
   }
diff --git a/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java b/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java
index 8d6b8d2..06cba56 100644
--- a/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java
+++ b/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8;
 
+import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -57,8 +59,16 @@
             : containsString("AbstractMethodError");
     testForR8(parameters.getBackend())
         .addProgramClasses(Main.class)
+        .allowDiagnosticWarningMessages(
+            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.O))
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
+        .compile()
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                equalTo(
+                    "Lambda expression implements missing interface `java.util.function.Supplier`"),
+                containsString("required for default or static interface methods desugaring")))
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(expectedError);
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index e04a2c1..9e54272 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -5,7 +5,9 @@
 
 import static com.android.tools.r8.TestBase.Backend.DEX;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.TestBase.Backend;
@@ -78,6 +80,10 @@
 
   public abstract TestDiagnosticMessages getDiagnosticMessages();
 
+  public abstract String getStdout();
+
+  public abstract String getStderr();
+
   public OutputMode getOutputMode() {
     return outputMode;
   }
@@ -268,11 +274,20 @@
     return self();
   }
 
+  public CR assertDiagnosticMessageThatMatches(Matcher<String> matcher) {
+    getDiagnosticMessages().assertDiagnosticMessageThatMatches(matcher);
+    return self();
+  }
+
   public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
     getDiagnosticMessages().assertInfoMessageThatMatches(matcher);
     return self();
   }
 
+  public CR assertAllInfoMessagesMatch(Matcher<String> matcher) {
+    return assertNoInfoMessageThatMatches(not(matcher));
+  }
+
   public CR assertNoInfoMessageThatMatches(Matcher<String> matcher) {
     getDiagnosticMessages().assertNoInfoMessageThatMatches(matcher);
     return self();
@@ -283,6 +298,11 @@
     return self();
   }
 
+  public CR assertAllWarningMessagesMatch(Matcher<String> matcher) {
+    getDiagnosticMessages().assertNoWarningMessageThatMatches(not(matcher));
+    return self();
+  }
+
   public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) {
     getDiagnosticMessages().assertNoWarningMessageThatMatches(matcher);
     return self();
@@ -298,6 +318,26 @@
     return self();
   }
 
+  public CR assertNoStdout() {
+    assertEquals("", getStdout());
+    return self();
+  }
+
+  public CR assertStdoutThatMatches(Matcher<String> matcher) {
+    assertThat(getStdout(), matcher);
+    return self();
+  }
+
+  public CR assertNoStderr() {
+    assertEquals("", getStderr());
+    return self();
+  }
+
+  public CR assertStderrThatMatches(Matcher<String> matcher) {
+    assertThat(getStderr(), matcher);
+    return self();
+  }
+
   public CR disassemble(PrintStream ps) throws IOException, ExecutionException {
     ToolHelper.disassemble(app, ps);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 8c6e7c2..c216670 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.ForwardingOutputStream;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.base.Suppliers;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.file.Path;
@@ -42,13 +44,16 @@
   final Backend backend;
 
   // Default initialized setup. Can be overwritten if needed.
+  private boolean allowStdoutMessages = false;
+  private boolean allowStderrMessages = false;
   private boolean useDefaultRuntimeLibrary = true;
   private final List<Path> additionalRunClassPath = new ArrayList<>();
   private ProgramConsumer programConsumer;
   private StringConsumer mainDexListConsumer;
   private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
   private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
-  private PrintStream stdout = null;
+  private ByteArrayOutputStream stdout = null;
+  private ByteArrayOutputStream stderr = null;
   protected OutputMode outputMode = OutputMode.DexIndexed;
 
   TestCompilerBuilder(TestState state, B builder, Backend backend) {
@@ -92,16 +97,32 @@
       }
     }
     PrintStream oldOut = System.out;
+    PrintStream oldErr = System.err;
+    CR cr = null;
     try {
       if (stdout != null) {
-        System.setOut(stdout);
+        System.setOut(new PrintStream(new ForwardingOutputStream(stdout, System.out)));
       }
-      CR cr = internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
+      if (stderr != null) {
+        System.setErr(new PrintStream(new ForwardingOutputStream(stderr, System.err)));
+      }
+      cr = internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
       cr.addRunClasspathFiles(additionalRunClassPath);
       return cr;
     } finally {
       if (stdout != null) {
+        getState().setStdout(stdout.toString());
         System.setOut(oldOut);
+        if (cr != null && !allowStdoutMessages) {
+          cr.assertNoStdout();
+        }
+      }
+      if (stderr != null) {
+        getState().setStderr(stderr.toString());
+        System.setErr(oldErr);
+        if (cr != null && !allowStderrMessages) {
+          cr.assertNoStderr();
+        }
       }
     }
   }
@@ -288,12 +309,28 @@
     return self();
   }
 
-  public T redirectStdOut(PrintStream printStream) {
-    assert stdout == null;
-    stdout = printStream;
+  public T allowStdoutMessages() {
+    allowStdoutMessages = true;
     return self();
   }
 
+  public T collectStdout() {
+    assert stdout == null;
+    stdout = new ByteArrayOutputStream();
+    return allowStdoutMessages();
+  }
+
+  public T allowStderrMessages() {
+    allowStdoutMessages = true;
+    return self();
+  }
+
+  public T collectStderr() {
+    assert stderr == null;
+    stderr = new ByteArrayOutputStream();
+    return allowStderrMessages();
+  }
+
   public T enableCoreLibraryDesugaring(AndroidApiLevel minAPILevel) {
     return enableCoreLibraryDesugaring(minAPILevel, null);
   }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 702815a..0dd7e67 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -9,35 +9,41 @@
 
 public interface TestDiagnosticMessages {
 
-  public List<Diagnostic> getInfos();
+  List<Diagnostic> getInfos();
 
-  public List<Diagnostic> getWarnings();
+  List<Diagnostic> getWarnings();
 
-  public List<Diagnostic> getErrors();
+  List<Diagnostic> getErrors();
 
-  public TestDiagnosticMessages assertNoMessages();
+  TestDiagnosticMessages assertNoMessages();
 
-  public TestDiagnosticMessages assertOnlyInfos();
+  TestDiagnosticMessages assertOnlyInfos();
 
-  public TestDiagnosticMessages assertOnlyWarnings();
+  TestDiagnosticMessages assertOnlyWarnings();
 
-  public TestDiagnosticMessages assertOnlyErrors();
+  TestDiagnosticMessages assertOnlyErrors();
 
-  public TestDiagnosticMessages assertInfosCount(int count);
+  TestDiagnosticMessages assertInfosCount(int count);
 
-  public TestDiagnosticMessages assertWarningsCount(int count);
+  TestDiagnosticMessages assertWarningsCount(int count);
 
-  public TestDiagnosticMessages assertErrorsCount(int count);
+  TestDiagnosticMessages assertErrorsCount(int count);
 
-  public TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertDiagnosticMessageThatMatches(Matcher<String> matcher);
 
-  public TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher);
 
-  public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertAllInfoMessagesMatch(Matcher<String> matcher);
 
-  public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher);
 
-  public TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher);
 
-  public TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
+  TestDiagnosticMessages assertAllWarningMessagesMatch(Matcher<String> matcher);
+
+  TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher);
+
+  TestDiagnosticMessages assertErrorMessageThatMatches(Matcher<String> matcher);
+
+  TestDiagnosticMessages assertNoErrorMessageThatMatches(Matcher<String> matcher);
 }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 478a60d..44f046b 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -4,11 +4,13 @@
 
 package com.android.tools.r8;
 
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.List;
 import org.hamcrest.Matcher;
@@ -122,22 +124,24 @@
   }
 
   private TestDiagnosticMessages assertMessageThatMatches(
-      List<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
-    assertNotEquals(0, diagnostics.size());
-    for (int i = 0; i < diagnostics.size(); i++) {
-      if (matcher.matches(diagnostics.get(i).getDiagnosticMessage())) {
+      Iterable<Diagnostic> diagnostics, String tag, Matcher<String> matcher) {
+    int numberOfDiagnostics = 0;
+    for (Diagnostic diagnostic : diagnostics) {
+      if (matcher.matches(diagnostic.getDiagnosticMessage())) {
         return this;
       }
+      numberOfDiagnostics++;
     }
+    assertNotEquals(0, numberOfDiagnostics);
     StringBuilder builder = new StringBuilder("No " + tag + " matches " + matcher.toString());
     builder.append(System.lineSeparator());
     if (getWarnings().size() == 0) {
       builder.append("There were no " + tag + "s.");
     } else {
-      builder.append("There were " + diagnostics.size() + " "+ tag + "s:");
+      builder.append("There were " + numberOfDiagnostics + " " + tag + "s:");
       builder.append(System.lineSeparator());
-      for (int i = 0; i < diagnostics.size(); i++) {
-        builder.append(diagnostics.get(i).getDiagnosticMessage());
+      for (Diagnostic diagnostic : diagnostics) {
+        builder.append(diagnostic.getDiagnosticMessage());
         builder.append(System.lineSeparator());
       }
     }
@@ -156,18 +160,38 @@
     return this;
   }
 
+  @Override
+  public TestDiagnosticMessages assertDiagnosticMessageThatMatches(Matcher<String> matcher) {
+    return assertMessageThatMatches(
+        Iterables.concat(getInfos(), getWarnings(), getErrors()), "diagnostic message", matcher);
+  }
+
+  @Override
   public TestDiagnosticMessages assertInfoMessageThatMatches(Matcher<String> matcher) {
     return assertMessageThatMatches(getInfos(), "info", matcher);
   }
 
+  @Override
+  public TestDiagnosticMessages assertAllInfoMessagesMatch(Matcher<String> matcher) {
+    return assertNoInfoMessageThatMatches(not(matcher));
+  }
+
+  @Override
   public TestDiagnosticMessages assertNoInfoMessageThatMatches(Matcher<String> matcher) {
     return assertNoMessageThatMatches(getInfos(), "info", matcher);
   }
 
+  @Override
   public TestDiagnosticMessages assertWarningMessageThatMatches(Matcher<String> matcher) {
     return assertMessageThatMatches(getWarnings(), "warning", matcher);
   }
 
+  @Override
+  public TestDiagnosticMessages assertAllWarningMessagesMatch(Matcher<String> matcher) {
+    return assertNoWarningMessageThatMatches(not(matcher));
+  }
+
+  @Override
   public TestDiagnosticMessages assertNoWarningMessageThatMatches(Matcher<String> matcher) {
     return assertNoMessageThatMatches(getWarnings(), "warning", matcher);
   }
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 35d37cc..b29d851 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -160,6 +160,10 @@
     return withApiFilter(api -> true);
   }
 
+  public TestParametersBuilder withApiLevel(AndroidApiLevel api) {
+    return withApiFilter(api::equals);
+  }
+
   public TestParametersBuilder withApiLevelsStartingAtIncluding(AndroidApiLevel startInclusive) {
     return withApiFilter(api -> startInclusive.getLevel() <= api.getLevel());
   }
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
index bdb4e2f..78388bc 100644
--- a/src/test/java/com/android/tools/r8/TestState.java
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -12,6 +12,9 @@
   private final TemporaryFolder temp;
   private final TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
 
+  private String stdout;
+  private String stderr;
+
   public TestState(TemporaryFolder temp) {
     this.temp = temp;
   }
@@ -27,4 +30,20 @@
   public TestDiagnosticMessages getDiagnosticsMessages() {
     return messages;
   }
+
+  public String getStdout() {
+    return stdout;
+  }
+
+  void setStdout(String stdout) {
+    this.stdout = stdout;
+  }
+
+  public String getStderr() {
+    return stderr;
+  }
+
+  void setStderr(String stderr) {
+    this.stderr = stderr;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index ddb0608..1bde24a 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.google.common.io.ByteStreams.toByteArray;
+import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -103,7 +104,14 @@
           .setMode(mode)
           .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
           .addKeepRuleFiles(MAIN_KEEP)
+          .allowDiagnosticInfoMessages(mode == CompilationMode.DEBUG)
           .compile()
+          .assertAllInfoMessagesMatch(
+              anyOf(
+                  containsString("Stripped invalid locals information from 1 method."),
+                  containsString("Methods with invalid locals information:"),
+                  containsString(
+                      "Some warnings are typically a sign of using an outdated Java toolchain.")))
           .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
           .writeToZip(jar);
     }
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index 5cbb40d..363008d 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -180,13 +180,12 @@
         .addProgramClassFileData(junitClasses)
         .addKeepAllClassesRule()
         .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
-        .compileWithExpectedDiagnostics(
+        .allowDiagnosticWarningMessages(libraryContainsJUnit())
+        .compile()
+        .inspectDiagnosticMessages(
             diagnostics -> {
               if (libraryContainsJUnit()) {
-                diagnostics.assertOnlyWarnings();
                 checkDiagnostics(diagnostics.getWarnings());
-              } else {
-                diagnostics.assertNoMessages();
               }
             })
         .inspect(this::testCaseClassInResult);
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 09489c9..e0ea969 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.TestBase;
@@ -140,14 +141,17 @@
             "classmerging.ArrayTypeCollisionTest$A",
             "classmerging.ArrayTypeCollisionTest$B");
     runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(
+                getProguardConfig(
+                    EXAMPLE_KEEP,
+                    "-neverinline public class classmerging.ArrayTypeCollisionTest {",
+                    "  static void method(...);",
+                    "}"))
+            .allowDiagnosticInfoMessages(),
         main,
         programFiles,
-        preservedClassNames::contains,
-        getProguardConfig(
-            EXAMPLE_KEEP,
-            "-neverinline public class classmerging.ArrayTypeCollisionTest {",
-            "  static void method(...);",
-            "}"));
+        preservedClassNames::contains);
   }
 
   /**
@@ -199,17 +203,18 @@
 
     // Run test.
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(
+                "-keep class " + main + " {",
+                "  public static void main(...);",
+                "}",
+                "-neverinline class " + main + " {",
+                "  static classmerging.A[] method(...);",
+                "  static classmerging.B[] method(...);",
+                "}"),
         main,
         jasminBuilder.build(),
-        preservedClassNames::contains,
-        StringUtils.joinLines(
-            "-keep class " + main + " {",
-            "  public static void main(...);",
-            "}",
-            "-neverinline class " + main + " {",
-            "  static classmerging.A[] method(...);",
-            "  static classmerging.B[] method(...);",
-            "}"));
+        preservedClassNames::contains);
   }
 
   // This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
@@ -231,7 +236,13 @@
     Set<String> preservedClassNames =
         ImmutableSet.of("classmerging.CallGraphCycleTest", "classmerging.CallGraphCycleTest$B");
     for (int i = 0; i < 5; i++) {
-      runTest(main, programFiles, preservedClassNames::contains);
+      runTest(
+          testForR8(Backend.DEX)
+              .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+              .allowDiagnosticInfoMessages(),
+          main,
+          programFiles,
+          preservedClassNames::contains);
     }
   }
 
@@ -250,10 +261,12 @@
             "classmerging.ConflictInGeneratedNameTest$B");
     CodeInspector inspector =
         runTestOnInput(
+            testForR8(Backend.DEX)
+                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                .allowDiagnosticInfoMessages(),
             main,
             readProgramFiles(programFiles),
             preservedClassNames::contains,
-            getProguardConfig(EXAMPLE_KEEP),
             options -> {
               configure(options);
               // Avoid that direct methods in B get inlined.
@@ -321,7 +334,13 @@
         ImmutableSet.of(
             "classmerging.FieldCollisionTest",
             "classmerging.FieldCollisionTest$B");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   @Test
@@ -342,10 +361,12 @@
             "classmerging.LambdaRewritingTest$FunctionImpl",
             "classmerging.LambdaRewritingTest$InterfaceImpl");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         name -> preservedClassNames.contains(name) || name.contains("$Lambda$"),
-        getProguardConfig(JAVA8_EXAMPLE_KEEP),
         options -> {
           this.configure(options);
           options.enableClassInlining = false;
@@ -367,10 +388,12 @@
             "classmerging.ConflictingInterfaceSignaturesTest",
             "classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         preservedClassNames::contains,
-        getProguardConfig(EXAMPLE_KEEP),
         options -> {
           this.configure(options);
           options.enableInlining = false;
@@ -399,7 +422,13 @@
             "classmerging.MethodCollisionTest$B",
             "classmerging.MethodCollisionTest$C",
             "classmerging.MethodCollisionTest$D");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   @Test
@@ -418,7 +447,12 @@
             "classmerging.NestedDefaultInterfaceMethodsTest$B",
             "classmerging.NestedDefaultInterfaceMethodsTest$C");
     runTest(
-        main, programFiles, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   @Test
@@ -436,14 +470,16 @@
             "classmerging.NestedDefaultInterfaceMethodsTest$B",
             "classmerging.NestedDefaultInterfaceMethodsTest$C");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         AndroidApp.builder()
             .addProgramFiles(programFiles)
             .addClassProgramData(
                 NestedDefaultInterfaceMethodsTestDump.CDump.dump(), Origin.unknown())
             .build(),
-        preservedClassNames::contains,
-        getProguardConfig(JAVA8_EXAMPLE_KEEP));
+        preservedClassNames::contains);
   }
 
   @Test
@@ -463,10 +499,12 @@
             "classmerging.PinnedParameterTypesTest$InterfaceImpl",
             "classmerging.PinnedParameterTypesTest$TestClass");
     runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"))
+            .allowDiagnosticInfoMessages(),
         main,
         programFiles,
-        preservedClassNames::contains,
-        getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+        preservedClassNames::contains);
   }
 
   @Test
@@ -486,10 +524,12 @@
             "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
             "classmerging.PinnedArrayParameterTypesTest$TestClass");
     runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"))
+            .allowDiagnosticInfoMessages(),
         main,
         programFiles,
-        preservedClassNames::contains,
-        getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+        preservedClassNames::contains);
   }
 
   @Test
@@ -515,10 +555,12 @@
                 "    1:1:void <init>():20:20 -> <init>",
                 "    1:1:void test():23:23 -> test");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         Predicates.alwaysTrue(),
-        getProguardConfig(EXAMPLE_KEEP),
         options -> {
           configure(options);
           options.enableVerticalClassMerging = false;
@@ -546,10 +588,12 @@
     Set<String> preservedClassNames =
         ImmutableSet.of("classmerging.ProguardFieldMapTest", "classmerging.ProguardFieldMapTest$B");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         preservedClassNames::contains,
-        getProguardConfig(EXAMPLE_KEEP),
         options -> {
           configure(options);
           options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
@@ -583,10 +627,12 @@
                 "    1:1:void <init>():22:22 -> <init>",
                 "    1:2:void method():26:27 -> method");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         Predicates.alwaysTrue(),
-        getProguardConfig(EXAMPLE_KEEP),
         options -> {
           configure(options);
           options.enableVerticalClassMerging = false;
@@ -617,10 +663,12 @@
         ImmutableSet.of(
             "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         preservedClassNames::contains,
-        getProguardConfig(EXAMPLE_KEEP),
         options -> {
           configure(options);
           options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
@@ -655,12 +703,16 @@
                 "    2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
                 "    2:2:void method():27 -> method");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(
+                getProguardConfig(
+                    EXAMPLE_KEEP,
+                    "-forceinline class classmerging.ProguardMethodMapTest$A { public void"
+                        + " method(); }"))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         Predicates.alwaysTrue(),
-        getProguardConfig(
-            EXAMPLE_KEEP,
-            "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
         options -> {
           configure(options);
           options.enableVerticalClassMerging = false;
@@ -688,12 +740,16 @@
         ImmutableSet.of(
             "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(
+                getProguardConfig(
+                    EXAMPLE_KEEP,
+                    "-forceinline class classmerging.ProguardMethodMapTest$A { public void"
+                        + " method(); }"))
+            .allowDiagnosticInfoMessages(),
         main,
         readProgramFiles(programFiles),
         preservedClassNames::contains,
-        getProguardConfig(
-            EXAMPLE_KEEP,
-            "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
         options -> {
           configure(options);
           options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
@@ -717,7 +773,13 @@
         ImmutableSet.of(
             "classmerging.SubClassThatReferencesSuperMethod",
             "classmerging.SuperCallRewritingTest");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   // When a subclass A has been merged into its subclass B, we rewrite invoke-super calls that hit
@@ -924,10 +986,11 @@
 
     // Run test.
     runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(String.format("-keep class %s { public static void main(...); }", main)),
         main,
         appBuilder.build(),
-        preservedClassNames::contains,
-        String.format("-keep class %s { public static void main(...); }", main));
+        preservedClassNames::contains);
   }
 
   @Test
@@ -953,10 +1016,12 @@
               "classmerging.SyntheticBridgeSignaturesTest$ASub",
               "classmerging.SyntheticBridgeSignaturesTest$BSub");
       runTestOnInput(
+          testForR8(Backend.DEX)
+              .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+              .allowDiagnosticInfoMessages(),
           main,
           readProgramFiles(programFiles),
           preservedClassNames::contains,
-          getProguardConfig(EXAMPLE_KEEP),
           options -> {
             this.configure(options);
             if (!allowInlining) {
@@ -989,7 +1054,13 @@
         ImmutableSet.of(
             "classmerging.ConflictingInterfaceSignaturesTest",
             "classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   // If an exception class A is merged into another exception class B, then all exception tables
@@ -1010,7 +1081,14 @@
             "classmerging.ExceptionTest",
             "classmerging.ExceptionTest$ExceptionB",
             "classmerging.ExceptionTest$Exception2");
-    CodeInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
+    CodeInspector inspector =
+        runTest(
+            testForR8(Backend.DEX)
+                .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+                .allowDiagnosticInfoMessages(),
+            main,
+            programFiles,
+            preservedClassNames::contains);
 
     ClassSubject mainClass = inspector.clazz(main);
     assertThat(mainClass, isPresent());
@@ -1061,7 +1139,13 @@
         method.getMethod().getCode().asCfCode().toString(),
         containsString("invokeinterface classmerging.MergeDefaultMethodIntoClassTest$A.f()V"));
 
-    runTestOnInput(main, app, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+    runTestOnInput(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(JAVA8_EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        app,
+        preservedClassNames::contains);
   }
 
   @Test
@@ -1079,7 +1163,13 @@
             "classmerging.ClassWithNativeMethodTest",
             "classmerging.ClassWithNativeMethodTest$A",
             "classmerging.ClassWithNativeMethodTest$B");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   @Test
@@ -1104,7 +1194,13 @@
             "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   @Test
@@ -1130,13 +1226,16 @@
     // Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as
     // a result of inlining).
     runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(
+                getProguardConfig(
+                    EXAMPLE_KEEP,
+                    "-allowaccessmodification",
+                    "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever"))
+            .allowDiagnosticInfoMessages(),
         main,
         programFiles,
-        preservedClassNames::contains,
-        getProguardConfig(
-            EXAMPLE_KEEP,
-            "-allowaccessmodification",
-            "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever"));
+        preservedClassNames::contains);
   }
 
   // TODO(christofferqa): This test checks that the invoke-super instruction in B is not rewritten
@@ -1158,11 +1257,14 @@
             "classmerging.RewritePinnedMethodTest$A",
             "classmerging.RewritePinnedMethodTest$C");
     runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(
+                getProguardConfig(
+                    EXAMPLE_KEEP, "-keep class classmerging.RewritePinnedMethodTest$A { *; }"))
+            .allowDiagnosticInfoMessages(),
         main,
         programFiles,
-        preservedClassNames::contains,
-        getProguardConfig(
-            EXAMPLE_KEEP, "-keep class classmerging.RewritePinnedMethodTest$A { *; }"));
+        preservedClassNames::contains);
   }
 
   @Test
@@ -1177,57 +1279,60 @@
     Set<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.TemplateMethodTest", "classmerging.TemplateMethodTest$AbstractClassImpl");
-    runTest(main, programFiles, preservedClassNames::contains);
+    runTest(
+        testForR8(Backend.DEX)
+            .addKeepRules(getProguardConfig(EXAMPLE_KEEP))
+            .allowDiagnosticInfoMessages(),
+        main,
+        programFiles,
+        preservedClassNames::contains);
   }
 
   private CodeInspector runTest(
-      String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Throwable {
-    return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP));
-  }
-
-  private CodeInspector runTest(
+      R8FullTestBuilder builder,
       String main,
       Path[] programFiles,
-      Predicate<String> preservedClassNames,
-      String proguardConfig)
+      Predicate<String> preservedClassNames)
       throws Throwable {
-    return runTestOnInput(
-        main, readProgramFiles(programFiles), preservedClassNames, proguardConfig);
+    return runTestOnInput(builder, main, readProgramFiles(programFiles), preservedClassNames);
   }
 
   private CodeInspector runTestOnInput(
-      String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
+      R8FullTestBuilder builder,
+      String main,
+      AndroidApp input,
+      Predicate<String> preservedClassNames)
       throws Throwable {
-    return runTestOnInput(main, input, preservedClassNames, proguardConfig, this::configure);
+    return runTestOnInput(builder, main, input, preservedClassNames, this::configure);
   }
 
   private CodeInspector runTestOnInput(
+      R8FullTestBuilder builder,
       String main,
       AndroidApp input,
       Predicate<String> preservedClassNames,
-      String proguardConfig,
       Consumer<InternalOptions> optionsConsumer)
       throws Throwable {
     return runTestOnInput(
+        builder,
         main,
         input,
         preservedClassNames,
-        proguardConfig,
         optionsConsumer,
         new VerticalClassMergerDebugTest(main));
   }
 
   private CodeInspector runTestOnInput(
+      R8FullTestBuilder builder,
       String main,
       AndroidApp input,
       Predicate<String> preservedClassNames,
-      String proguardConfig,
       Consumer<InternalOptions> optionsConsumer,
       VerticalClassMergerDebugTest debugTestRunner)
       throws Throwable {
     Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
     R8TestCompileResult compileResult =
-        testForR8(Backend.DEX)
+        builder
             .apply(
                 b -> {
                   // Some tests add DEX inputs, so circumvent the check by adding directly to the
@@ -1238,7 +1343,6 @@
                   }
                 })
             .noMinification()
-            .addKeepRules(proguardConfig)
             .enableProguardTestOptions()
             .addOptionsModification(
                 o -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index dbdc654..a79ccf7 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
 import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -50,7 +51,9 @@
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR_11)
         .addKeepRuleFiles(MAIN_KEEP)
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
+        .allowDiagnosticWarningMessages()
         .compile()
+        .assertAllWarningMessagesMatch(containsString("Missing class:"))
         .inspect(this::assertNotEmpty)
         .inspect(Java11R8CompilationTest::assertNoNests);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index 48fe91b..75982e9 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -69,7 +69,11 @@
   }
 
   private TestCompileResult compileOnlyClassesMatching(
-      Matcher<String> matcher, boolean d8, boolean ignoreMissingClasses) throws Exception {
+      Matcher<String> matcher,
+      boolean d8,
+      boolean allowDiagnosticWarningMessages,
+      boolean ignoreMissingClasses)
+      throws Exception {
     List<Path> matchingClasses =
         CLASS_NAMES.stream()
             .filter(matcher::matches)
@@ -93,6 +97,7 @@
                 options.enableNestBasedAccessDesugaring = true;
                 options.ignoreMissingClasses = ignoreMissingClasses;
               })
+          .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages)
           .compile();
     }
   }
@@ -101,7 +106,7 @@
     try {
       Matcher<String> innerClassMatcher =
           containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
-      compileOnlyClassesMatching(innerClassMatcher, false, false);
+      compileOnlyClassesMatching(innerClassMatcher, false, false, false);
       fail("Should have raised an exception for missing nest host");
     } catch (Exception e) {
       assertTrue(e.getCause().getCause().getMessage().contains("requires its nest host"));
@@ -111,7 +116,7 @@
   private void testIncompleteNestError() {
     try {
       Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
-      compileOnlyClassesMatching(innerClassMatcher, false, false);
+      compileOnlyClassesMatching(innerClassMatcher, false, false, false);
       fail("Should have raised an exception for incomplete nest");
     } catch (Exception e) {
       assertTrue(e.getCause().getCause().getMessage().contains("requires its nest mates"));
@@ -121,7 +126,7 @@
   private void testMissingNestHostWarning(boolean d8, boolean desugarWarning) throws Exception {
     Matcher<String> innerClassMatcher =
         containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
-    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, true);
+    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
     if (desugarWarning) {
       assertTrue(
@@ -137,7 +142,7 @@
 
   private void testIncompleteNestWarning(boolean d8, boolean desugarWarning) throws Exception {
     Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
-    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, true);
+    TestCompileResult compileResult = compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true);
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
     if (desugarWarning) {
       assertTrue(
@@ -145,9 +150,7 @@
               .anyMatch(warn -> warn instanceof IncompleteNestNestDesugarDiagnosic));
     } else if (!d8) {
       // R8 should raise extra warning when cleaning the nest.
-      assertTrue(
-          compileResult.getDiagnosticMessages().getWarnings().stream()
-              .anyMatch(warn -> warn.getDiagnosticMessage().contains("requires its nest mates")));
+      compileResult.assertWarningMessageThatMatches(containsString("requires its nest mates"));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
index 247e8a1..9c4872f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingAnalysisTest.java
@@ -59,6 +59,7 @@
             .enableInliningAnnotations()
             .addKeepRules(KEEP_ENUM)
             .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspect(this::assertEnumsAsExpected);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
index 762380a..e4f7bd1 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
@@ -46,16 +45,13 @@
 
   @Test
   public void testEnumUnboxingFailure() throws Exception {
-    R8FullTestBuilder r8FullTestBuilder =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class);
-    for (Class<?> failure : FAILURES) {
-      r8FullTestBuilder.addKeepMainRule(failure);
-    }
     R8TestCompileResult compile =
-        r8FullTestBuilder
+        testForR8(parameters.getBackend())
+            .addInnerClasses(FailingMethodEnumUnboxingAnalysisTest.class)
+            .addKeepMainRules(FAILURES)
             .addKeepRules(KEEP_ENUM)
             .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
             .addOptionsModification(
                 // Disabled to avoid toString() being removed.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
index 2841689..8811ee5 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -40,6 +40,7 @@
             .addKeepMainRules(INPUTS)
             .addKeepRules(KEEP_ENUM)
             .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .noMinification()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
index 079c9f7..2570a12 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingAnalysisTest.java
@@ -37,6 +37,7 @@
             .addKeepMainRule(classToTest)
             .addKeepRules(KEEP_ENUM)
             .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspectDiagnosticMessages(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
index 87cacd5..2abd358 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/PhiEnumUnboxingAnalysisTest.java
@@ -39,6 +39,7 @@
             .addKeepRules(KEEP_ENUM)
             .enableInliningAnnotations()
             .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspectDiagnosticMessages(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
index 699c72d..53cddc6 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingAnalysisTest.java
@@ -39,6 +39,7 @@
             .addKeepRules(KEEP_ENUM)
             .enableInliningAnnotations()
             .addOptionsModification(this::enableEnumOptions)
+            .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspectDiagnosticMessages(
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 6bf5d8f..0788985 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.internal.proto;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -91,10 +93,15 @@
                   options.enableStringSwitchConversion = true;
                 })
             .allowAccessModification()
+            .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
+            .assertAllInfoMessagesMatch(
+                containsString("Proguard configuration rule does not match anything"))
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
             .inspect(this::inspect);
 
     for (String main : mains) {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 558664f..1473e03 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.internal.proto;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -88,10 +91,15 @@
                   options.enableStringSwitchConversion = true;
                 })
             .allowAccessModification(allowAccessModification)
+            .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
             .minification(enableMinification)
             .setMinApi(parameters.getApiLevel())
             .compile()
+            .assertAllInfoMessagesMatch(
+                containsString("Proguard configuration rule does not match anything"))
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
             .inspect(
                 outputInspector -> {
                   verifyMapAndRequiredFieldsAreKept(inputInspector, outputInspector);
@@ -356,10 +364,17 @@
               options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
             })
         .allowAccessModification(allowAccessModification)
+        .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
         .minification(enableMinification)
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .assertAllInfoMessagesMatch(
+            containsString("Proguard configuration rule does not match anything"))
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
+                containsString("required for default or static interface methods desugaring")))
         .inspect(
             inspector ->
                 assertRewrittenProtoSchemasMatch(new CodeInspector(PROGRAM_FILES), inspector));
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index faa91e5..7a261ff 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -5,6 +5,9 @@
 package com.android.tools.r8.internal.proto;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -64,10 +67,14 @@
               options.enableStringSwitchConversion = true;
             })
         .allowAccessModification(allowAccessModification)
+        .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
         .minification(enableMinification)
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .assertAllInfoMessagesMatch(
+            containsString("Proguard configuration rule does not match anything"))
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .inspect(
             outputInspector -> {
               verifyUnusedFieldsAreRemoved(inputInspector, outputInspector);
@@ -113,10 +120,17 @@
               options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
             })
         .allowAccessModification(allowAccessModification)
+        .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
         .minification(enableMinification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
+        .assertAllInfoMessagesMatch(
+            containsString("Proguard configuration rule does not match anything"))
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
+                containsString("required for default or static interface methods desugaring")))
         .inspect(
             inspector ->
                 assertRewrittenProtoSchemasMatch(new CodeInspector(PROGRAM_FILES), inspector));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index 901a577..41ce8a8 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -5,19 +5,20 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.NeverMerge;
-import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Throwables;
-import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,7 +36,8 @@
 
   @Parameters(name = "{1}, allow type errors: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public MissingClassesJoinTest(boolean allowTypeErrors, TestParameters parameters) {
@@ -45,57 +47,63 @@
 
   @Test
   public void test() throws Exception {
-    Path classpathFile =
-        testForD8()
-            .addProgramClasses(ASub2.class)
-            .setMinApi(parameters.getRuntime())
-            .compile()
-            .writeToZip();
-
     if (parameters.isDexRuntime() && !allowTypeErrors) {
-      testForD8()
-          // Intentionally not adding ASub2 as a program class.
-          .addProgramClasses(A.class, ASub1.class, Box.class, TestClass.class)
-          .setMinApi(parameters.getRuntime())
-          .compile()
-          .addRunClasspathFiles(classpathFile)
+      D8TestCompileResult compileResult =
+          testForD8()
+              // Intentionally not adding ASub2 as a program class.
+              .addProgramClasses(A.class, ASub1.class, Box.class, TestClass.class)
+              .setMinApi(parameters.getApiLevel())
+              .compile();
+
+      testForRuntime(parameters)
+          .addProgramFiles(compileResult.writeToZip())
+          .addProgramClasses(ASub2.class)
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutput(expectedOutput);
     }
 
     try {
-      R8TestRunResult result =
+      R8TestCompileResult compileResult =
           testForR8(parameters.getBackend())
               // Intentionally not adding ASub2 as a program class.
               .addProgramClasses(A.class, ASub1.class, Box.class, TestClass.class)
               .addKeepAllClassesRule()
               .addOptionsModification(options -> options.testing.allowTypeErrors = allowTypeErrors)
+              .allowDiagnosticWarningMessages()
               .enableMergeAnnotations()
-              .setMinApi(parameters.getRuntime())
+              .setMinApi(parameters.getApiLevel())
               .compile()
-              .addRunClasspathFiles(classpathFile)
-              .run(parameters.getRuntime(), TestClass.class);
+              .assertAllWarningMessagesMatch(
+                  equalTo(
+                      "The method `void "
+                          + TestClass.class.getTypeName()
+                          + ".main(java.lang.String[])` does not type check and will be assumed to"
+                          + " be unreachable."));
 
       // Compilation fails unless type errors are allowed.
       assertTrue(allowTypeErrors);
 
-      // TestClass.main() does not type check, so it should have been replaced by `throw null`.
-      // Note that, even if we do not replace the body of main() with `throw null`, the code would
-      // still not work for the CF backend:
-      //
-      //     java.lang.VerifyError: Bad type on operand stack
-      //     Exception Details:
-      //       Location:
-      //         MissingClassesJoinTest$TestClass.main([Ljava/lang/String;)V @28: putstatic
-      //       Reason:
-      //         Type 'java/lang/Object' (current frame, stack[0]) is not assignable to
-      //         'com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest$A'
-      //       Current Frame:
-      //         bci: @28
-      //         flags: { }
-      //         locals: { 'java/lang/Object' }
-      //         stack: { 'java/lang/Object' }
-      result.assertFailureWithErrorThatMatches(containsString("NullPointerException"));
+      testForRuntime(parameters)
+          .addProgramFiles(compileResult.writeToZip())
+          .addProgramClasses(ASub2.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          // TestClass.main() does not type check, so it should have been replaced by `throw null`.
+          // Note that, even if we do not replace the body of main() with `throw null`, the code
+          // would still not work for the CF backend:
+          //
+          //     java.lang.VerifyError: Bad type on operand stack
+          //     Exception Details:
+          //       Location:
+          //         MissingClassesJoinTest$TestClass.main([Ljava/lang/String;)V @28: putstatic
+          //       Reason:
+          //         Type 'java/lang/Object' (current frame, stack[0]) is not assignable to
+          //         'com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest$A'
+          //       Current Frame:
+          //         bci: @28
+          //         flags: { }
+          //         locals: { 'java/lang/Object' }
+          //         stack: { 'java/lang/Object' }
+          .assertFailureWithErrorThatMatches(containsString("NullPointerException"));
     } catch (CompilationFailedException e) {
       // Compilation should only fail when type errors are not allowed.
       assertFalse(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
index 08d4f85..98a6e9e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
@@ -4,25 +4,21 @@
 
 package com.android.tools.r8.ir.optimize.inliner;
 
-import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.google.common.collect.Streams;
-import java.util.List;
 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 Regress131349148 extends TestBase {
 
@@ -60,12 +56,18 @@
   public void testNoInlineNonExistingCatchPreL() throws Exception {
     R8TestRunResult result =
         testForR8(parameters.getBackend())
-            .addProgramClasses(TestClassCallingMethodWithNonExisting.class,
-                ClassWithCatchNonExisting.class, ExistingException.class)
+            .addProgramClasses(
+                TestClassCallingMethodWithNonExisting.class,
+                ClassWithCatchNonExisting.class,
+                ExistingException.class)
             .addKeepMainRule(TestClassCallingMethodWithNonExisting.class)
             .addKeepRules("-dontwarn " + NonExistingException.class.getTypeName())
+            .allowDiagnosticWarningMessages(
+                parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N))
             .setMinApi(parameters.getApiLevel())
             .compile()
+            .assertAllWarningMessagesMatch(
+                containsString("required for default or static interface methods desugaring"))
             .run(parameters.getRuntime(), TestClassCallingMethodWithNonExisting.class)
             .assertSuccess();
     ClassSubject classSubject =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java
index 4e4624a..86745e5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/InstanceFieldLoadsSeparatedByInvokeCustomTest.java
@@ -4,10 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
 
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,6 +31,7 @@
     return getTestParameters()
         .withCfRuntimes()
         .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.O)
         .build();
   }
 
@@ -40,8 +44,10 @@
     testForR8(parameters.getBackend())
         .addProgramClassFileData(InstanceFieldLoadsSeparatedByInvokeCustomTestClassGenerator.dump())
         .addKeepAllClassesRule()
-        .setMinApi(parameters.getRuntime())
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters.getApiLevel())
         .compile()
+        .assertAllWarningMessagesMatch(containsString("Unknown bootstrap method"))
         .run(parameters.getRuntime(), "InstanceFieldLoadsSeparatedByInvokeCustomTestClass")
         .assertSuccess();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
index 8896197..8b33acb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
@@ -143,7 +143,7 @@
   @Parameters(name = "{0} minify:{1} {2}")
   public static Collection<Object[]> parameters() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         BooleanUtils.values(),
         TestNamingConfig.values());
   }
@@ -159,6 +159,23 @@
     this.config = config;
   }
 
+  public boolean hasMalformedInnerClassAttribute() {
+    switch (config) {
+      case DEFAULT:
+      case OUTER_ENDS_WITH_DOLLAR:
+      case $_$_$:
+      case DOLLAR2_SEPARATOR:
+        return false;
+      case EMTPY_SEPARATOR:
+      case UNDERBAR_SEPARATOR:
+      case NON_NESTED_INNER:
+      case WRONG_REPACKAGE:
+        return true;
+      default:
+        throw new Unreachable("Unexpected test configuration: " + config);
+    }
+  }
+
   private void checkWarningsAboutMalformedAttribute(TestCompileResult<?, ?> result) {
     switch (config) {
       case DEFAULT:
@@ -188,7 +205,7 @@
         testForD8()
             .addProgramClassFileData(InnerClassNameTestDump.dump(config, parameters))
             .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
     checkWarningsAboutMalformedAttribute(d8CompileResult);
     D8TestRunResult d8RunResult = d8CompileResult.run(parameters.getRuntime(), MAIN_CLASS);
@@ -211,11 +228,13 @@
             .addKeepRules("-keep,allowobfuscation class * { *; }")
             .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
             .addProgramClassFileData(InnerClassNameTestDump.dump(config, parameters))
+            .allowDiagnosticInfoMessages(hasMalformedInnerClassAttribute())
             .minification(minify)
             .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
-            .setMinApi(parameters.getRuntime())
-            .compile();
-    checkWarningsAboutMalformedAttribute(r8CompileResult);
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .apply(this::checkWarningsAboutMalformedAttribute);
+
     CodeInspector inspector = r8CompileResult.inspector();
     R8TestRunResult r8RunResult = r8CompileResult.run(parameters.getRuntime(), MAIN_CLASS);
     switch (config) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
index 1c46c19..4062cb0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8.kotlin.lambda;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -54,11 +56,14 @@
         .addLibraryFiles(ToolHelper.getKotlinStdlibJar())
         .addProgramFiles(ktClasses)
         .addKeepMainRule("**.B143165163Kt")
+        .allowDiagnosticInfoMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
         // TODO(b/143165163): better not output info like this.
-        .assertInfoMessageThatMatches(containsString("Unrecognized Kotlin lambda"))
-        .assertInfoMessageThatMatches(containsString("unexpected static method"))
+        .assertAllInfoMessagesMatch(
+            allOf(
+                containsString("Unrecognized Kotlin lambda"),
+                containsString("unexpected static method")))
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar())
         .run(parameters.getRuntime(), pkg + ".B143165163Kt")
         .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
@@ -80,11 +85,15 @@
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .addProgramFiles(ktClasses)
         .addKeepMainRule("**.B143165163Kt")
+        .allowDiagnosticMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
         // TODO(b/143165163): better not output info like this.
-        .assertInfoMessageThatMatches(containsString("Unrecognized Kotlin lambda"))
-        .assertInfoMessageThatMatches(containsString("does not implement any interfaces"))
+        .assertAllInfoMessagesMatch(
+            allOf(
+                containsString("Unrecognized Kotlin lambda"),
+                containsString("does not implement any interfaces")))
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), pkg + ".B143165163Kt")
         .assertSuccessWithOutputLines("outer foo bar", "outer foo default");
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index c2f9bd9..2c42f2a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -6,6 +6,8 @@
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -88,9 +90,14 @@
             // Keep the main entry.
             .addKeepMainRule(main)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .allowDiagnosticWarningMessages()
             // -dontoptimize so that basic code structure is kept.
             .noOptimization()
-            .compile();
+            .compile()
+            .assertAllWarningMessagesMatch(
+                anyOf(
+                    equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
+                    equalTo("Resource 'META-INF/main.kotlin_module' already exists.")));
     final String extClassName = pkg + ".libtype_lib_ext.ExtKt";
     compileResult.inspect(inspector -> {
       ClassSubject ext = inspector.clazz(extClassName);
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 6c745bf..f93179f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -51,7 +52,11 @@
             .addKeepMainRule(mainClassName)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addKeepRules("-keep class kotlin.Metadata")
+            .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
             .run(parameters.getRuntime(), mainClassName);
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(mainClassName);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 3806c05..8bc0e79 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.withNativeFileSeparators;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 
 import com.android.tools.r8.GenerateMainDexList;
 import com.android.tools.r8.GenerateMainDexListCommand;
@@ -328,12 +329,14 @@
         .addKeepRules("-keepattributes *Annotation*")
         .addMainDexRuleFiles(mainDexRules)
         .addOptionsModification(optionsConsumer)
+        .allowDiagnosticWarningMessages()
         .assumeAllMethodsMayHaveSideEffects()
         .setMinApi(minSdk)
         .noMinification()
         .noTreeShaking()
         .setMainDexListConsumer(ToolHelper.consumeString(r8MainDexListOutput::set))
         .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .writeToZip(out);
 
     List<String> r8MainDexList =
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
index 3778392..bf33b1c 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -48,6 +48,7 @@
         .addKeepMainRule(mainClass)
         // Include explicit main dex entry for class Static.
         .addMainDexListClasses(Static.class)
+        .allowDiagnosticWarningMessages()
         .compile()
         .inspect(this::classStaticGone)
         .assertOnlyWarnings()
@@ -66,6 +67,7 @@
         .addMainDexListClasses(Main.class, Static.class)
         // Include main dex rule for class Static2.
         .addMainDexClassRules(Static2.class)
+        .allowDiagnosticWarningMessages()
         .compile()
         .inspect(this::classStaticGone)
         .assertOnlyWarnings()
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index d56ae8e..7e15524 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -33,7 +34,7 @@
   @Parameterized.Parameters(name = "{0} target: {1} minify: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         KotlinTargetVersion.values(),
         BooleanUtils.values());
   }
@@ -52,8 +53,12 @@
             .addProgramFiles(getKotlinJarFile(FOLDER))
             .addProgramFiles(getJavaJarFile(FOLDER))
             .addKeepMainRule(MAIN_CLASS_NAME)
+            .allowDiagnosticWarningMessages()
             .minification(minify)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
             .run(parameters.getRuntime(), MAIN_CLASS_NAME)
             .inspector();
     ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index e9383ef..e5a152e 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static com.android.tools.r8.utils.DescriptorUtils.isValidJavaType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -46,6 +47,7 @@
   private final String appFileName;
   private final List<String> keepRulesFiles;
   private final BiConsumer<TestParameters, CodeInspector> inspection;
+  private final String test;
 
   public IdentifierMinifierTest(
       TestParameters parameters,
@@ -56,18 +58,34 @@
     this.appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + FileUtils.JAR_EXTENSION;
     this.keepRulesFiles = keepRulesFiles;
     this.inspection = inspection;
+    this.test = test;
   }
 
   @Test
   public void identiferMinifierTest() throws Exception {
+    boolean hasWarning =
+        test.equals("identifiernamestring") && keepRulesFiles.get(0).endsWith("keep-rules-2.txt");
     CodeInspector codeInspector =
         testForR8(parameters.getBackend())
             .addProgramFiles(Paths.get(appFileName))
             .addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
             .allowUnusedProguardConfigurationRules()
+            .apply(
+                builder -> {
+                  if (hasWarning) {
+                    builder.allowDiagnosticWarningMessages();
+                  }
+                })
             .enableProguardTestOptions()
             .setMinApi(parameters.getRuntime())
             .compile()
+            .inspectDiagnosticMessages(
+                diagnostics -> {
+                  if (hasWarning) {
+                    diagnostics.assertAllWarningMessagesMatch(
+                        containsString("Cannot determine what identifier string flows to"));
+                  }
+                })
             .inspector();
     inspection.accept(parameters, codeInspector);
   }
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 2b06482..e9ce1d8 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -3,11 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.code.AputObject;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ConstClass;
@@ -53,7 +57,7 @@
         "-keep class " + CLASS_NAME,
         "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
         "-dontoptimize");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -86,7 +90,7 @@
         "-keep class " + CLASS_NAME,
         "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
         "-dontoptimize");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -129,7 +133,7 @@
         "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
         "-keep,allowobfuscation class " + BOO,
         "-dontoptimize");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -167,7 +171,7 @@
         "-keep class " + CLASS_NAME,
         "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { static <fields>; }",
         "-dontoptimize");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -199,7 +203,7 @@
         "-keep class " + CLASS_NAME,
         "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { static <fields>; }",
         "-dontoptimize");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -238,7 +242,7 @@
         "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { static <fields>; }",
         "-keep,allowobfuscation class " + BOO,
         "-dontoptimize");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -268,7 +272,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-dontshrink");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -290,7 +294,7 @@
         "-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep,allowobfuscation class " + BOO,
         "-dontshrink");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -314,7 +318,7 @@
         "-keep class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
         "-keep,allowobfuscation class " + BOO + " { <fields>; }",
         "-dontshrink");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -338,7 +342,7 @@
         "-keep class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
         "-keep,allowobfuscation class " + BOO + " { <methods>; }",
         "-dontshrink");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -365,10 +369,20 @@
         "invoke-static {v0, v1}, LExample;->foo(Ljava/lang/String;Ljava/lang/String;)V",
         "return-void");
 
-    List<String> pgConfigs = ImmutableList.of(
-        "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
-        "-keep class " + CLASS_NAME);
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector =
+        compileWithR8(
+                builder,
+                testBuilder ->
+                    testBuilder
+                        .addKeepRules(
+                            "-identifiernamestring class "
+                                + CLASS_NAME
+                                + " { static void foo(...); }",
+                            "-keep class " + CLASS_NAME)
+                        .allowDiagnosticWarningMessages())
+            .assertAllWarningMessagesMatch(
+                containsString("Cannot determine what 'Mixed/form.Boo' refers to"))
+            .inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -410,7 +424,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
         "-keep class " + CLASS_NAME);
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -456,7 +470,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class " + BOO);
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -510,7 +524,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -560,7 +574,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -617,7 +631,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -678,7 +692,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = compileWithR8(builder, pgConfigs).inspector();
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -702,13 +716,17 @@
     assertNotEquals("foo", constString.getString().toString());
   }
 
-  private CodeInspector getInspectorAfterRunR8(
+  private R8TestCompileResult compileWithR8(
       SmaliBuilder builder, List<String> proguardConfigurations) throws Exception {
+    return compileWithR8(builder, testBuilder -> testBuilder.addKeepRules(proguardConfigurations));
+  }
+
+  private R8TestCompileResult compileWithR8(
+      SmaliBuilder builder, ThrowableConsumer<R8FullTestBuilder> configuration) throws Exception {
     return testForR8(Backend.DEX)
         .addProgramDexFileData(builder.compile())
-        .addKeepRules(proguardConfigurations)
+        .apply(configuration)
         .debug()
-        .compile()
-        .inspector();
+        .compile();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/InvalidObfuscationEntryTest.java b/src/test/java/com/android/tools/r8/naming/InvalidObfuscationEntryTest.java
index 982267f..57bd145 100644
--- a/src/test/java/com/android/tools/r8/naming/InvalidObfuscationEntryTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InvalidObfuscationEntryTest.java
@@ -48,7 +48,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public InvalidObfuscationEntryTest(TestParameters parameters) {
@@ -65,13 +65,10 @@
         .addKeepRules("-obfuscationdictionary " + dictionary.toString())
         .addKeepAllClassesRuleWithAllowObfuscation()
         .addKeepMainRule(Main.class)
-        .setMinApi(parameters.getRuntime())
+        .allowDiagnosticInfoMessages()
+        .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(
-            testDiagnosticMessages -> {
-              testDiagnosticMessages.assertInfoMessageThatMatches(
-                  containsString("Invalid character"));
-            })
+        .assertInfoMessageThatMatches(containsString("Invalid character"))
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("Hello from a", "Hello from b")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 7828143..daa21a4 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -67,12 +68,16 @@
   public void test_example3() throws Exception {
     TestKotlinClass ex3 = new TestKotlinClass("intrinsics_identifiers.Example3Kt");
     String mainClassName = ex3.getClassName();
-    TestCompileResult result = testForR8(Backend.DEX)
-        .addProgramFiles(getKotlinJarFile(FOLDER))
-        .addProgramFiles(getJavaJarFile(FOLDER))
-        .addKeepMainRule(mainClassName)
-        .minification(minification)
-        .compile();
+    TestCompileResult result =
+        testForR8(Backend.DEX)
+            .addProgramFiles(getKotlinJarFile(FOLDER))
+            .addProgramFiles(getJavaJarFile(FOLDER))
+            .addKeepMainRule(mainClassName)
+            .allowDiagnosticWarningMessages()
+            .minification(minification)
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
     CodeInspector codeInspector = result.inspector();
     MethodSubject main = codeInspector.clazz(ex3.getClassName()).mainMethod();
     assertThat(main, isPresent());
@@ -116,18 +121,23 @@
       String targetFieldName,
       String targetMethodName) throws Exception {
     String mainClassName = testMain.getClassName();
-    TestRunResult result = testForR8(Backend.DEX)
-        .addProgramFiles(getKotlinJarFile(FOLDER))
-        .addProgramFiles(getJavaJarFile(FOLDER))
-        .enableProguardTestOptions()
-        .addKeepMainRule(mainClassName)
-        .addKeepRules(StringUtils.lines(
-            "-neverclassinline class **." + targetClassName,
-            "-nevermerge class **." + targetClassName,
-            "-neverinline class **." + targetClassName + " { <methods>; }"
-        ))
-        .minification(minification)
-        .run(mainClassName);
+    TestRunResult result =
+        testForR8(Backend.DEX)
+            .addProgramFiles(getKotlinJarFile(FOLDER))
+            .addProgramFiles(getJavaJarFile(FOLDER))
+            .enableProguardTestOptions()
+            .addKeepMainRule(mainClassName)
+            .addKeepRules(
+                StringUtils.lines(
+                    "-neverclassinline class **." + targetClassName,
+                    "-nevermerge class **." + targetClassName,
+                    "-neverinline class **." + targetClassName + " { <methods>; }"))
+            .allowDiagnosticWarningMessages()
+            .minification(minification)
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .run(mainClassName);
     CodeInspector codeInspector = result.inspector();
 
     MethodSubject main = codeInspector.clazz(testMain.getClassName()).mainMethod();
diff --git a/src/test/java/com/android/tools/r8/proguard/configuration/UnusedKeepRuleTest.java b/src/test/java/com/android/tools/r8/proguard/configuration/UnusedKeepRuleTest.java
index 44e95ec..26811a4 100644
--- a/src/test/java/com/android/tools/r8/proguard/configuration/UnusedKeepRuleTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/configuration/UnusedKeepRuleTest.java
@@ -21,6 +21,7 @@
         .addKeepRules("-keep class NotPresent")
         .addOptionsModification(
             options -> options.testing.reportUnusedProguardConfigurationRules = true)
+        .allowDiagnosticMessages()
         .allowUnusedProguardConfigurationRules()
         .compile()
         .inspectDiagnosticMessages(
diff --git a/src/test/java/com/android/tools/r8/proguard/rules/InnerClassNameSeparatorTest.java b/src/test/java/com/android/tools/r8/proguard/rules/InnerClassNameSeparatorTest.java
index 6da44a5..bfe9fe4 100644
--- a/src/test/java/com/android/tools/r8/proguard/rules/InnerClassNameSeparatorTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/rules/InnerClassNameSeparatorTest.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
@@ -27,7 +28,7 @@
   @Parameterized.Parameters(name = "{0}, separator: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(), ImmutableList.of("$", "."));
+        getTestParameters().withAllRuntimesAndApiLevels().build(), ImmutableList.of("$", "."));
   }
 
   public InnerClassNameSeparatorTest(TestParameters parameters, String separator) {
@@ -37,17 +38,42 @@
 
   @Test
   public void testR8() throws Exception {
-    TestRunResult<?> result =
-        runTest(
-            testForR8(parameters.getBackend())
-                .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
-                .addOptionsModification(
-                    options -> {
-                      if (separator.equals(".")) {
-                        // R8 currently does not recognize the '.' as an inner class name separator.
-                        options.testing.allowUnusedProguardConfigurationRules = true;
-                      }
-                    }));
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addOptionsModification(InternalOptions::disableNameReflectionOptimization)
+            .addOptionsModification(
+                options -> {
+                  if (separator.equals(".")) {
+                    // R8 currently does not recognize the '.' as an inner class name separator.
+                    options.testing.allowUnusedProguardConfigurationRules = true;
+                  }
+                })
+            .apply(
+                builder -> {
+                  if (separator.equals(".")) {
+                    builder.allowDiagnosticInfoMessages();
+                  }
+                })
+            .addProgramClassesAndInnerClasses(InnerClassNameSeparatorTestClass.class)
+            .addKeepMainRule(InnerClassNameSeparatorTestClass.class)
+            .addKeepRules(
+                "-keep,allowobfuscation class "
+                    + InnerClassNameSeparatorTestClass.class.getTypeName()
+                    + separator
+                    + InnerClassNameSeparatorTestClass.Inner.class.getSimpleName()
+                    + " {",
+                "  <init>(...);",
+                "}")
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspectDiagnosticMessages(
+                diagnostics -> {
+                  if (separator.equals(".")) {
+                    diagnostics.assertAllInfoMessagesMatch(
+                        containsString("Proguard configuration rule does not match anything"));
+                  }
+                })
+            .run(parameters.getRuntime(), InnerClassNameSeparatorTestClass.class);
     if (separator.equals("$")) {
       result.assertSuccessWithOutputLines("Hello world!");
     } else {
@@ -73,7 +99,7 @@
                 + " {",
             "  <init>(...);",
             "}")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), InnerClassNameSeparatorTestClass.class);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/LibraryExtendsProgramRefinedReceiverIsLibraryClass.java b/src/test/java/com/android/tools/r8/resolution/LibraryExtendsProgramRefinedReceiverIsLibraryClass.java
index db1864c..2bd5e75 100644
--- a/src/test/java/com/android/tools/r8/resolution/LibraryExtendsProgramRefinedReceiverIsLibraryClass.java
+++ b/src/test/java/com/android/tools/r8/resolution/LibraryExtendsProgramRefinedReceiverIsLibraryClass.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.resolution;
 
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -47,9 +49,11 @@
         .enableInliningAnnotations()
         .addKeepClassRules(ProgramClass.class)
         .addKeepMainRule(ProgramTestRunnerWithoutPhi.class)
+        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .debug()
         .compile()
+        .assertAllWarningMessagesMatch(containsString("extends program class"))
         .addRunClasspathClasses(LibraryClass.class)
         .run(parameters.getRuntime(), ProgramTestRunnerWithoutPhi.class)
         .assertSuccessWithOutput(StringUtils.lines("SUCCESS"));
@@ -64,8 +68,10 @@
         .enableInliningAnnotations()
         .addKeepClassRules(ProgramClass.class)
         .addKeepMainRule(ProgramTestRunnerWithPhi.class)
+        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .assertAllWarningMessagesMatch(containsString("extends program class"))
         .addRunClasspathClasses(LibraryClass.class)
         .run(parameters.getRuntime(), ProgramTestRunnerWithPhi.class)
         .assertSuccessWithOutput(StringUtils.lines("SUCCESS"));
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 5d2a2d8..13190d9 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -10,6 +10,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 
@@ -86,8 +87,11 @@
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(MAIN)
+        .allowDiagnosticWarningMessages()
         .noMinification()
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), MAIN)
         .assertFailureWithErrorThatMatches(containsString("main"))
         .inspectStackTrace(
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index e2fe820..2d9f47b 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -11,6 +11,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 
@@ -81,9 +82,12 @@
         .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
+        .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
         .inspectStackTrace(
@@ -106,9 +110,12 @@
         .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
+        .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionInstance"))
         .inspectStackTrace(
@@ -131,9 +138,12 @@
         .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
+        .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
         .inspectStackTrace(
@@ -158,9 +168,12 @@
         .addProgramFiles(compilationResults.apply(parameters.getRuntime()))
         .addProgramFiles(ToolHelper.getKotlinStdlibJar())
         .addKeepAttributes("SourceFile", "LineNumberTable")
+        .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .run(parameters.getRuntime(), main)
         .assertFailureWithErrorThatMatches(containsString("inlineExceptionStatic"))
         .inspectStackTrace(
diff --git a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
index 5143949..b299b76 100644
--- a/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/JavaScriptScriptEngineTest.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.rewrite;
 
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
@@ -26,7 +29,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public JavaScriptScriptEngineTest(TestParameters parameters) {
@@ -41,7 +44,7 @@
     assumeTrue("Only run D8 for dex backend", parameters.isDexRuntime());
     testForD8()
         .addInnerClasses(JavaScriptScriptEngineTest.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .apply(this::addRhinoForAndroid)
         .compile()
         .run(parameters.getRuntime(), TestClassWithExplicitRhinoScriptEngineRegistration.class)
@@ -53,15 +56,21 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(JavaScriptScriptEngineTest.class)
         .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .apply(
             b -> {
               if (parameters.isDexRuntime()) {
                 addRhinoForAndroid(b);
                 addKeepRulesForAndroidRhino(b);
+                b.allowDiagnosticWarningMessages();
               }
             })
         .compile()
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                containsString("Missing class:"),
+                containsString("required for default or static interface methods desugaring"),
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(
             parameters.isCfRuntime() ? EXPECTED_NASHORN_OUTPUT : EXPECTED_RHINO_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
index 4e451ee..c7037ca 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ScriptEngineTest.java
@@ -4,12 +4,14 @@
 
 package com.android.tools.r8.rewrite;
 
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DataEntryResource;
-import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.origin.Origin;
@@ -41,7 +43,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ScriptEngineTest(TestParameters parameters) {
@@ -51,33 +53,37 @@
   @Test
   public void test() throws IOException, CompilationFailedException, ExecutionException {
     Path path = temp.newFile("out.zip").toPath();
-    R8FullTestBuilder builder =
-        testForR8(parameters.getBackend())
-            .addInnerClasses(ScriptEngineTest.class)
-            .addKeepMainRule(TestClass.class)
-            .setMinApi(parameters.getRuntime())
-            .addDataEntryResources(
-                DataEntryResource.fromBytes(
-                    StringUtils.lines(MyScriptEngine1FactoryImpl.class.getTypeName()).getBytes(),
-                    "META-INF/services/" + ScriptEngineFactory.class.getTypeName(),
-                    Origin.unknown()))
-            .addDataEntryResources(
-                DataEntryResource.fromBytes(
-                    StringUtils.lines(MyScriptEngine2FactoryImpl.class.getTypeName()).getBytes(),
-                    "META-INF/services/" + ScriptEngineFactory.class.getTypeName(),
-                    Origin.unknown()))
-            .apply(
-                b -> {
-                  if (parameters.isDexRuntime()) {
-                    addRhinoForAndroid(b);
-                  }
-                })
-            // TODO(b/136633154): This should work both with and without -dontobfuscate.
-            .noMinification()
-            // TODO(b/136633154): This should work both with and without -dontshrink.
-            .noTreeShaking();
-    builder
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ScriptEngineTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(MyScriptEngine1FactoryImpl.class.getTypeName()).getBytes(),
+                "META-INF/services/" + ScriptEngineFactory.class.getTypeName(),
+                Origin.unknown()))
+        .addDataEntryResources(
+            DataEntryResource.fromBytes(
+                StringUtils.lines(MyScriptEngine2FactoryImpl.class.getTypeName()).getBytes(),
+                "META-INF/services/" + ScriptEngineFactory.class.getTypeName(),
+                Origin.unknown()))
+        .apply(
+            b -> {
+              if (parameters.isDexRuntime()) {
+                addRhinoForAndroid(b);
+                b.allowDiagnosticWarningMessages();
+              }
+            })
+        // TODO(b/136633154): This should work both with and without -dontobfuscate.
+        .noMinification()
+        // TODO(b/136633154): This should work both with and without -dontshrink.
+        .noTreeShaking()
         .compile()
+        .assertAllWarningMessagesMatch(
+            anyOf(
+                containsString("Missing class:"),
+                containsString("it is required for default or static interface methods desugaring"),
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))
         .writeToZip(path)
         .run(parameters.getRuntime(), TestClass.class)
         // TODO(b/136633154): This should provide 2 script engines on both runtimes. The use of
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index b42be41..5b119ce 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.core.IsNot.not;
 
 import com.android.tools.r8.D8TestRunResult;
@@ -79,7 +80,9 @@
   @Parameters(name = "{0}, mode: {1}, use interface: {2}")
   public static Collection<Object[]> parameters() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(), Mode.values(), BooleanUtils.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        Mode.values(),
+        BooleanUtils.values());
   }
 
   @Test
@@ -171,7 +174,8 @@
     jasminBuilder.writeJar(inputJar);
 
     if (parameters.isCfRuntime()) {
-      TestRunResult<?> jvmResult = testForJvm().addClasspath(inputJar).run(mainClass.name);
+      TestRunResult<?> jvmResult =
+          testForJvm().addClasspath(inputJar).run(parameters.getRuntime(), mainClass.name);
       checkTestRunResult(jvmResult, Compiler.JAVAC);
 
       ProguardTestRunResult proguardResult =
@@ -184,10 +188,12 @@
     } else {
       assert parameters.isDexRuntime();
 
-      DXTestRunResult dxResult = testForDX().addProgramFiles(inputJar).run(mainClass.name);
+      DXTestRunResult dxResult =
+          testForDX().addProgramFiles(inputJar).run(parameters.getRuntime(), mainClass.name);
       checkTestRunResult(dxResult, Compiler.DX);
 
-      D8TestRunResult d8Result = testForD8().addProgramFiles(inputJar).run(mainClass.name);
+      D8TestRunResult d8Result =
+          testForD8().addProgramFiles(inputJar).run(parameters.getRuntime(), mainClass.name);
       checkTestRunResult(d8Result, Compiler.D8);
     }
 
@@ -205,7 +211,14 @@
                     options.testing.allowTypeErrors = true;
                   }
                 })
-            .setMinApi(parameters.getRuntime())
+            .allowDiagnosticWarningMessages(
+                mode == Mode.INVOKE_UNVERIFIABLE_METHOD && !useInterface)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo(
+                    "The method `void UnverifiableClass.unverifiableMethod()` does not type check"
+                        + " and will be assumed to be unreachable."))
             .run(parameters.getRuntime(), mainClass.name);
     checkTestRunResult(r8Result, Compiler.R8);
 
@@ -224,7 +237,14 @@
                   }
                   options.enableUninstantiatedTypeOptimizationForInterfaces = true;
                 })
-            .setMinApi(parameters.getRuntime())
+            .allowDiagnosticWarningMessages(
+                mode == Mode.INVOKE_UNVERIFIABLE_METHOD && !useInterface)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo(
+                    "The method `void UnverifiableClass.unverifiableMethod()` does not type check"
+                        + " and will be assumed to be unreachable."))
             .run(parameters.getRuntime(), mainClass.name);
     checkTestRunResult(
         r8ResultWithUninstantiatedTypeOptimizationForInterfaces,
diff --git a/src/test/java/com/android/tools/r8/shaking/UsageInformationConsumerTest.java b/src/test/java/com/android/tools/r8/shaking/UsageInformationConsumerTest.java
index 4c9b057..3355fab 100644
--- a/src/test/java/com/android/tools/r8/shaking/UsageInformationConsumerTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/UsageInformationConsumerTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -11,8 +12,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.StringUtils;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,16 +48,16 @@
 
   @Test
   public void testRule() throws Exception {
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
     testForR8(parameters.getBackend())
-        .redirectStdOut(new PrintStream(out))
+        .collectStdout()
         .addProgramClasses(TestClass.class, UnusedClass.class)
         .addKeepClassAndMembersRules(TestClass.class)
         .addKeepRules("-printusage")
         .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertStdoutThatMatches(equalTo(StringUtils.lines(UnusedClass.class.getTypeName())))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
-    assertEquals(StringUtils.lines(UnusedClass.class.getTypeName()), out.toString());
   }
 
   static class UnusedClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index 5c6a305..84ceffa 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -62,7 +63,7 @@
   @Parameterized.Parameters(name = "{0} target: {1} minify: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         KotlinTargetVersion.values(),
         BooleanUtils.values());
   }
@@ -87,20 +88,22 @@
 
   @Test
   public void b120951621_keepAll() throws Exception {
-    CodeInspector inspector = testForR8(parameters.getBackend())
-        .addProgramFiles(getKotlinJarFile(FOLDER))
-        .addProgramFiles(getJavaJarFile(FOLDER))
-        .addKeepMainRule(MAIN_CLASS_NAME)
-        .addKeepRules(KEEP_ANNOTATIONS)
-        .addKeepRules(
-            "-keep @interface " + ANNOTATION_NAME + " {",
-            "  *;",
-            "}"
-        )
-        .minification(minify)
-        .setMinApi(parameters.getRuntime())
-        .run(parameters.getRuntime(), MAIN_CLASS_NAME)
-        .assertSuccessWithOutput(JAVA_OUTPUT).inspector();
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(getKotlinJarFile(FOLDER))
+            .addProgramFiles(getJavaJarFile(FOLDER))
+            .addKeepMainRule(MAIN_CLASS_NAME)
+            .addKeepRules(KEEP_ANNOTATIONS)
+            .addKeepRules("-keep @interface " + ANNOTATION_NAME + " {", "  *;", "}")
+            .allowDiagnosticWarningMessages()
+            .minification(minify)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .run(parameters.getRuntime(), MAIN_CLASS_NAME)
+            .assertSuccessWithOutput(JAVA_OUTPUT)
+            .inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertThat(clazz, not(isRenamed()));
@@ -126,20 +129,25 @@
 
   @Test
   public void b120951621_partiallyKeep() throws Exception {
-    CodeInspector inspector = testForR8(parameters.getBackend())
-        .addProgramFiles(getKotlinJarFile(FOLDER))
-        .addProgramFiles(getJavaJarFile(FOLDER))
-        .addKeepMainRule(MAIN_CLASS_NAME)
-        .addKeepRules(KEEP_ANNOTATIONS)
-        .addKeepRules(
-            "-keep,allowobfuscation @interface " + ANNOTATION_NAME + " {",
-            "  java.lang.String *f2();",
-            "}"
-        )
-        .minification(minify)
-        .setMinApi(parameters.getRuntime())
-        .run(parameters.getRuntime(), MAIN_CLASS_NAME)
-        .assertSuccessWithOutput(JAVA_OUTPUT).inspector();
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(getKotlinJarFile(FOLDER))
+            .addProgramFiles(getJavaJarFile(FOLDER))
+            .addKeepMainRule(MAIN_CLASS_NAME)
+            .addKeepRules(KEEP_ANNOTATIONS)
+            .addKeepRules(
+                "-keep,allowobfuscation @interface " + ANNOTATION_NAME + " {",
+                "  java.lang.String *f2();",
+                "}")
+            .allowDiagnosticWarningMessages()
+            .minification(minify)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .run(parameters.getRuntime(), MAIN_CLASS_NAME)
+            .assertSuccessWithOutput(JAVA_OUTPUT)
+            .inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertEquals(minify, clazz.isRenamed());
@@ -163,15 +171,21 @@
 
   @Test
   public void b120951621_keepAnnotation() throws Exception {
-    CodeInspector inspector = testForR8(parameters.getBackend())
-        .addProgramFiles(getKotlinJarFile(FOLDER))
-        .addProgramFiles(getJavaJarFile(FOLDER))
-        .addKeepMainRule(MAIN_CLASS_NAME)
-        .addKeepRules(KEEP_ANNOTATIONS)
-        .minification(minify)
-        .setMinApi(parameters.getRuntime())
-        .run(parameters.getRuntime(), MAIN_CLASS_NAME)
-        .assertSuccessWithOutput(JAVA_OUTPUT).inspector();
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(getKotlinJarFile(FOLDER))
+            .addProgramFiles(getJavaJarFile(FOLDER))
+            .addKeepMainRule(MAIN_CLASS_NAME)
+            .addKeepRules(KEEP_ANNOTATIONS)
+            .allowDiagnosticWarningMessages()
+            .minification(minify)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .run(parameters.getRuntime(), MAIN_CLASS_NAME)
+            .assertSuccessWithOutput(JAVA_OUTPUT)
+            .inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertEquals(minify, clazz.isRenamed());
@@ -195,14 +209,20 @@
 
   @Test
   public void b120951621_noKeep() throws Exception {
-    CodeInspector inspector = testForR8(parameters.getBackend())
-        .addProgramFiles(getKotlinJarFile(FOLDER))
-        .addProgramFiles(getJavaJarFile(FOLDER))
-        .addKeepMainRule(MAIN_CLASS_NAME)
-        .minification(minify)
-        .setMinApi(parameters.getRuntime())
-        .run(parameters.getRuntime(), MAIN_CLASS_NAME)
-        .assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION).inspector();
+    CodeInspector inspector =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(getKotlinJarFile(FOLDER))
+            .addProgramFiles(getJavaJarFile(FOLDER))
+            .addKeepMainRule(MAIN_CLASS_NAME)
+            .allowDiagnosticWarningMessages()
+            .minification(minify)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllWarningMessagesMatch(
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .run(parameters.getRuntime(), MAIN_CLASS_NAME)
+            .assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION)
+            .inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertEquals(minify, clazz.isRenamed());
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
index fd2696f..bd29735 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -274,11 +274,15 @@
         AndroidApiLevel.O_MR1,
         AndroidApiLevel.O_MR1,
         expectedResultForNative(AndroidApiLevel.O_MR1),
-        builder ->
-            builder.addOptionsModification(
-                options ->
-                    // android.os.Build$VERSION only exists in the Android runtime.
-                    options.testing.allowUnusedProguardConfigurationRules = backend == Backend.CF),
+        builder -> {
+          // android.os.Build$VERSION only exists in the Android runtime.
+          if (backend == Backend.CF) {
+            builder
+                .addOptionsModification(
+                    options -> options.testing.allowUnusedProguardConfigurationRules = true)
+                .allowDiagnosticInfoMessages();
+          }
+        },
         this::compatCodeNotPresent,
         ImmutableList.of(
             "-assumevalues class android.os.Build$VERSION { public static final int SDK_INT return "
@@ -307,9 +311,14 @@
           AndroidApiLevel.O_MR1,
           AndroidApiLevel.O_MR1,
           expectedResultForNative(AndroidApiLevel.O_MR1),
-          builder ->
-              builder.addOptionsModification(
-                  options -> options.testing.allowUnusedProguardConfigurationRules = true),
+          builder -> {
+            if (backend == Backend.CF) {
+              builder
+                  .addOptionsModification(
+                      options -> options.testing.allowUnusedProguardConfigurationRules = true)
+                  .allowDiagnosticInfoMessages();
+            }
+          },
           this::compatCodePresent,
           ImmutableList.of(rule),
           SynthesizedRule.NOT_PRESENT);
@@ -333,8 +342,10 @@
           AndroidApiLevel.O_MR1,
           expectedResultForNative(AndroidApiLevel.O_MR1),
           builder ->
-              builder.addOptionsModification(
-                  options -> options.testing.allowUnusedProguardConfigurationRules = true),
+              builder
+                  .addOptionsModification(
+                      options -> options.testing.allowUnusedProguardConfigurationRules = true)
+                  .allowDiagnosticInfoMessages(),
           this::compatCodeNotPresent,
           ImmutableList.of(rule),
           SynthesizedRule.PRESENT);
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index 6d0d767..4ce41ce 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.shaking.b134858535;
 
+import static org.hamcrest.CoreMatchers.containsString;
+
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -25,7 +27,9 @@
         .addProgramClassFileData(EventPublisher$bDump.dump())
         .addKeepClassRules(Interface.class)
         .addKeepMainRule(Main.class)
+        .allowDiagnosticInfoMessages()
         .setMinApi(AndroidApiLevel.L)
-        .compile();
+        .compile()
+        .assertAllWarningMessagesMatch(containsString("Unrecognized Kotlin lambda"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
index ab37cb0..27bb092 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
@@ -8,9 +8,14 @@
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @NeverMerge
 interface I {
@@ -34,68 +39,82 @@
   }
 }
 
+@RunWith(Parameterized.class)
 public class KeepRuleWarningTest extends TestBase {
+
   private static final Class<?> MAIN = KeepRuleWarningTestRunner.class;
   private static final String EXPECTED_OUTPUT = StringUtils.lines("static::foo", "default::bar");
 
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.L).build();
+  }
+
+  public KeepRuleWarningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test_allMethods() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
-        .setMinApi(AndroidApiLevel.L)
+        .setMinApi(parameters.getApiLevel())
         .enableMergeAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { <methods>; }")
         .compile()
         .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
-        .run(MAIN)
+        .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void test_asterisk() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
-        .setMinApi(AndroidApiLevel.L)
+        .setMinApi(parameters.getApiLevel())
         .enableMergeAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { *(); }")
         .compile()
         .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
-        .run(MAIN)
+        .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void test_stillNotSpecific() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
-        .setMinApi(AndroidApiLevel.L)
+        .setMinApi(parameters.getApiLevel())
         .enableMergeAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { *** f*(); }")
         .compile()
         .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
-        .run(MAIN)
+        .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void test_specific() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .addProgramClasses(I.class, C.class, MAIN)
-        .setMinApi(AndroidApiLevel.L)
+        .setMinApi(parameters.getApiLevel())
         .enableMergeAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { static void foo(); }")
+        .allowDiagnosticWarningMessages()
         .compile()
-        .inspectDiagnosticMessages(m -> {
-          m.assertWarningsCount(1)
-              .assertWarningMessageThatMatches(containsString("static void foo()"))
-              .assertWarningMessageThatMatches(containsString("is ignored"))
-              .assertWarningMessageThatMatches(containsString("will be desugared"));
-        })
-        .run(MAIN)
+        .inspectDiagnosticMessages(
+            m ->
+                m.assertWarningsCount(1)
+                    .assertWarningMessageThatMatches(containsString("static void foo()"))
+                    .assertWarningMessageThatMatches(containsString("is ignored"))
+                    .assertWarningMessageThatMatches(containsString("will be desugared")))
+        .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 8cb1cf0..203dd3a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking.ifrule;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -47,6 +48,10 @@
   }
 
   private TestShrinkerBuilder<?, ?, ?, ?, ?> getTestBuilder() {
+    return getTestBuilder(false);
+  }
+
+  private TestShrinkerBuilder<?, ?, ?, ?, ?> getTestBuilder(boolean allowDiagnosticInfoMessages) {
     switch (shrinker) {
       case PROGUARD6:
         assertTrue(parameters.isCfRuntime());
@@ -54,6 +59,7 @@
       case R8:
         return testForR8(parameters.getBackend())
             .addTestingAnnotationsAsProgramClasses()
+            .allowDiagnosticInfoMessages(allowDiagnosticInfoMessages)
             .allowUnusedProguardConfigurationRules()
             .enableNeverClassInliningAnnotations()
             .enableInliningAnnotations();
@@ -66,7 +72,7 @@
   public void ifOnPublic_noPublicClassForIfRule() throws Exception {
     assumeFalse(shrinker.isProguard() && parameters.isDexRuntime());
 
-    getTestBuilder()
+    getTestBuilder(shrinker.isR8())
         .addProgramClasses(CLASSES)
         .addKeepRules(
             "-repackageclasses 'top'",
@@ -81,6 +87,13 @@
             "}")
         .setMinApi(parameters.getApiLevel())
         .compile()
+        .apply(
+            compileResult -> {
+              if (shrinker.isR8()) {
+                compileResult.assertAllInfoMessagesMatch(
+                    containsString("Proguard configuration rule does not match anything"));
+              }
+            })
         .inspect(
             inspector -> {
               ClassSubject classSubject = inspector.clazz(ClassForIf.class);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
index 54fcadf..eab27a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
 import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -17,15 +18,32 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 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 IfRuleWithInterfaceMethodDesugaringTest extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.M).build();
+  }
+
+  public IfRuleWithInterfaceMethodDesugaringTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
     String expectedOutput =
@@ -34,7 +52,7 @@
     testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
 
     CodeInspector inspector =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addInnerClasses(IfRuleWithInterfaceMethodDesugaringTest.class)
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
@@ -46,12 +64,16 @@
                 "  !public !static void virtualMethod();",
                 "}",
                 "-keep class " + Unused2.class.getTypeName())
+            .allowDiagnosticInfoMessages()
             .allowUnusedProguardConfigurationRules()
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableMergeAnnotations()
-            .setMinApi(AndroidApiLevel.M)
-            .run(TestClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .assertAllInfoMessagesMatch(
+                containsString("Proguard configuration rule does not match anything"))
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput)
             .inspector();
 
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java
index 37ba854..9edc64d 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/RemovedClassTestRunner.java
@@ -5,20 +5,19 @@
 
 import static com.android.tools.r8.references.Reference.classFromClass;
 import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
 import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
 import com.google.common.collect.ImmutableList;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,15 +33,15 @@
 
   private static final String EXPECTED = StringUtils.lines("called bar");
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
   @Parameters(name = "{0}")
-  public static Backend[] data() {
-    return ToolHelper.getBackends();
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  public RemovedClassTestRunner(Backend backend) {
-    this.backend = backend;
+  public RemovedClassTestRunner(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
@@ -51,22 +50,24 @@
     MethodReference barMethod = methodFromMethod(CLASS.getDeclaredMethod("bar"));
     MethodReference bazMethod = methodFromMethod(CLASS.getDeclaredMethod("baz"));
 
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
     R8TestCompileResult compileResult =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .enableGraphInspector()
             .enableInliningAnnotations()
             .addProgramClasses(CLASSES)
             .addKeepMethodRules(mainMethod)
             .addKeepRules("-whyareyoukeeping class " + REMOVED_CLASS.getTypeName())
-            .redirectStdOut(new PrintStream(baos))
-            .compile();
-    String expectedOutput = StringUtils.lines("Nothing is keeping " + REMOVED_CLASS.getTypeName());
-    String compileOutput = new String(baos.toByteArray(), StandardCharsets.UTF_8);
-    assertEquals(expectedOutput, compileOutput);
+            .setMinApi(parameters.getApiLevel())
+            .collectStdout()
+            .compile()
+            .assertStdoutThatMatches(
+                equalTo(StringUtils.lines("Nothing is keeping " + REMOVED_CLASS.getTypeName())));
 
     GraphInspector inspector =
-        compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED).graphInspector();
+        compileResult
+            .run(parameters.getRuntime(), CLASS)
+            .assertSuccessWithOutput(EXPECTED)
+            .graphInspector();
 
     // The only root should be the keep main-method rule.
     assertEquals(1, inspector.getRoots().size());
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index f4ed50c..2387e7c 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -5,7 +5,6 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -13,7 +12,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Test;
@@ -40,7 +38,7 @@
     return getTestParameters().withNoneRuntime().build();
   }
 
-  final TestParameters parameters;
+  private final TestParameters parameters;
 
   public WhyAreYouKeepingAllTest(TestParameters parameters) {
     this.parameters = parameters;
@@ -53,13 +51,13 @@
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
         .addKeepRules(WHY_ARE_YOU_KEEPING_ALL)
-        .redirectStdOut(new PrintStream(baos))
-        .compile();
-    assertThat(baos.toString(), containsString("referenced in keep rule"));
-
-    // TODO(b/124655065): We should always know the reason for keeping.
-    // It is OK if this starts failing while the kept-graph API is incomplete, in which case replace
-    // the 'not(containsString(' by just 'containsString('.
-    assertThat(baos.toString(), not(containsString("kept for unknown reasons")));
+        .collectStdout()
+        .compile()
+        .assertStdoutThatMatches(containsString("referenced in keep rule"))
+        // TODO(b/124655065): We should always know the reason for keeping.
+        // It is OK if this starts failing while the kept-graph API is incomplete, in which case
+        // replace
+        // the 'not(containsString(' by just 'containsString('.
+        .assertStdoutThatMatches(not(containsString("kept for unknown reasons")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
index 2914c4e..39d3aaf 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingOverriddenMethodTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
@@ -45,7 +46,6 @@
 
   private void testViaConfig(Class<?> main, Class<?> targetClass, Class<?> subClass)
       throws Exception {
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
     testForR8(Backend.DEX)
         .addInnerClasses(WhyAreYouKeepingOverriddenMethodTest.class)
         .addKeepMainRule(main)
@@ -55,13 +55,12 @@
         .enableInliningAnnotations()
         .enableMergeAnnotations()
         .minification(minification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(AndroidApiLevel.B)
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
-        .redirectStdOut(new PrintStream(baos))
-        .compile();
-    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
-    assertThat(output, containsString(expectedMessage(targetClass)));
-    assertThat(output, not(containsString(expectedNotContainingMessage(subClass))));
+        .collectStdout()
+        .compile()
+        .assertStdoutThatMatches(containsString(expectedMessage(targetClass)))
+        .assertStdoutThatMatches(not(containsString(expectedNotContainingMessage(subClass))));
   }
 
   private void testViaConsumer(
@@ -74,7 +73,7 @@
         .enableInliningAnnotations()
         .enableMergeAnnotations()
         .minification(minification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(AndroidApiLevel.B)
         .setKeptGraphConsumer(graphConsumer)
         .compile();
 
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
index c230c00..92578d3 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.shaking.whyareyoukeeping;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverInline;
@@ -77,10 +79,9 @@
         .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName())
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
-        .redirectStdOut(new PrintStream(baos))
-        .compile();
-    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
-    assertEquals(expected, output);
+        .collectStdout()
+        .compile()
+        .assertStdoutThatMatches(equalTo(expected));
   }
 
   @Test
@@ -108,10 +109,9 @@
         .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName() + " { baz(); }")
         .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
-        .redirectStdOut(new PrintStream(baos))
-        .compile();
-    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
-    assertEquals(expected + expectedPathToBaz, output);
+        .collectStdout()
+        .compile()
+        .assertStdoutThatMatches(equalTo(expected + expectedPathToBaz));
   }
 
   @Test
@@ -138,13 +138,14 @@
         .addProgramClasses(A.class)
         .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         .addKeepRules("-whyareyoukeeping class NonExistentClass")
+        .allowDiagnosticInfoMessages()
         .allowUnusedProguardConfigurationRules()
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
-        .redirectStdOut(new PrintStream(baos))
-        .compile();
-    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
-    // Expected outcome is empty.
-    assertEquals("", output);
+        .collectStdout()
+        .compile()
+        .assertNoStdout()
+        .assertAllInfoMessagesMatch(
+            containsString("Proguard configuration rule does not match anything"));
   }
 
   @Test
@@ -156,10 +157,8 @@
         .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         .addKeepRules("-whyareyoukeeping class " + aName + " { nonExistentMethod(); }")
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
-        .redirectStdOut(new PrintStream(baos))
-        .compile();
-    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
-    // Expected outcome is empty.
-    assertFalse("b/122820741", output.isEmpty());
+        .collectStdout()
+        .compile()
+        .assertStdoutThatMatches(not(equalTo("")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/ForwardingOutputStream.java b/src/test/java/com/android/tools/r8/utils/ForwardingOutputStream.java
new file mode 100644
index 0000000..082c947
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ForwardingOutputStream.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2020, 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.utils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+
+public class ForwardingOutputStream extends OutputStream {
+
+  private final List<OutputStream> listeners;
+
+  public ForwardingOutputStream(OutputStream... listeners) {
+    this.listeners = Arrays.asList(listeners);
+  }
+
+  @Override
+  public void write(int b) throws IOException {
+    for (OutputStream out : listeners) {
+      out.write(b);
+    }
+  }
+
+  @Override
+  public void write(byte[] b) throws IOException {
+    for (OutputStream out : listeners) {
+      out.write(b);
+    }
+  }
+
+  @Override
+  public void write(byte[] b, int off, int len) throws IOException {
+    for (OutputStream out : listeners) {
+      out.write(b, off, len);
+    }
+  }
+
+  @Override
+  public void flush() throws IOException {
+    for (OutputStream out : listeners) {
+      out.flush();
+    }
+  }
+
+  @Override
+  public void close() throws IOException {
+    for (OutputStream out : listeners) {
+      out.close();
+    }
+  }
+}