Allow no output file for tracereferences CLI

Bug: 169127026
Bug: 169546956
Change-Id: I7f19620245f58fbc7df0b2b5f17f2d20b4d2fe94
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
index ce8f565..c778667 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommand.java
@@ -96,6 +96,10 @@
     return TraceReferencesCommandParser.parse(args, origin, diagnosticsHandler);
   }
 
+  public static Builder parse(Collection<String> args, Origin origin) {
+    return TraceReferencesCommandParser.parse(args.toArray(new String[args.size()]), origin);
+  }
+
   public boolean isPrintHelp() {
     return printHelp;
   }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
index f299459..e36ad33 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesCommandParser.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.BaseCompilerCommandParser;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.tracereferences.TraceReferencesFormattingConsumer.OutputFormat;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -24,29 +25,40 @@
 class TraceReferencesCommandParser {
 
   private static final Set<String> OPTIONS_WITH_PARAMETER =
-      ImmutableSet.of("--lib", "--target", "--source", "--format", "--output");
+      ImmutableSet.of("--lib", "--target", "--source", "--output");
 
   static final String USAGE_MESSAGE =
       String.join(
           "\n",
           Iterables.concat(
               Arrays.asList(
-                  "Usage: tracereferences [options] [@<argfile>]",
-                  " Each <argfile> is a file containing additional arguments (one per line)",
+                  "Usage: tracereferences <command> [<options>] [@<argfile>]",
+                  " Where <command> is one of:",
+                  "  --check                 # Run emitting only diagnostics messages.",
+                  "  --print-usage           # Traced references will be output in the print-usage",
+                  "                          # format.",
+                  "  --keep-rules [<keep-rules-options>]",
+                  "                          # Traced references will be output in the keep-rules",
+                  "                          # format.",
+                  " and each <argfile> is a file containing additional options (one per line)",
                   " and options are:",
                   "  --lib <file|jdk-home>   # Add <file|jdk-home> runtime library.",
                   "  --source <file>         # Add <file> as a source for tracing references.",
                   "  [--target <file>]       # Add <file> as a target for tracing references. When",
                   "                          # target is not specified all references from source",
-                  "                          # outside of library are treated as a missing"
-                      + " references.",
-                  "  [--format printuses|keep|keepallowobfuscation]",
-                  "                          # Format of the output. Default is 'printuses'.",
-                  "  --output <file>         # Output result in <outfile>."),
+                  "                          # outside of library are treated as a missing",
+                  "                          # references.",
+                  "  --output <file>         # Output result in <outfile>. If not passed the",
+                  "                          # result will go to standard out.",
+                  "                          # result will go to standard out."),
               BaseCompilerCommandParser.MAP_DIAGNOSTICS_USAGE_MESSAGE,
               Arrays.asList(
                   "  --version               # Print the version of tracereferences.",
-                  "  --help                  # Print this message.")));
+                  "  --help                  # Print this message.",
+                  " and <keep-rule-options> are:",
+                  "  --allowobfuscation      # Output keep rules with the allowobfuscation",
+                  "                          # modifier (defaults to rules without the"
+                      + " modifier)")));
   /**
    * Parse the tracereferences command-line.
    *
@@ -76,11 +88,43 @@
         .parse(args, origin, TraceReferencesCommand.builder(handler));
   }
 
+  private enum Command {
+    CHECK,
+    PRINTUSAGE,
+    KEEP_RULES;
+
+    private OutputFormat toOutputFormat(boolean allowobfuscation) {
+      switch (this) {
+        case PRINTUSAGE:
+          return OutputFormat.PRINTUSAGE;
+        case KEEP_RULES:
+          return allowobfuscation
+              ? OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION
+              : OutputFormat.KEEP_RULES;
+        default:
+          throw new Unreachable();
+      }
+    }
+  }
+
+  private void checkCommandNotSet(
+      Command command, TraceReferencesCommand.Builder builder, Origin origin) {
+    if (command != null) {
+      builder.error(new StringDiagnostic("Multiple commands specified", origin));
+    }
+  }
+
   private TraceReferencesCommand.Builder parse(
       String[] args, Origin origin, TraceReferencesCommand.Builder builder) {
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder::error);
     Path output = null;
-    OutputFormat format = null;
+    Command command = null;
+    boolean allowObfuscation = false;
+    if (expandedArgs.length == 0) {
+      builder.error(new StringDiagnostic("Missing command"));
+      return builder;
+    }
+    // Parse options.
     for (int i = 0; i < expandedArgs.length; i++) {
       String arg = expandedArgs[i].trim();
       String nextArg = null;
@@ -97,32 +141,33 @@
         continue;
       } else if (arg.equals("--help")) {
         builder.setPrintHelp(true);
+        return builder;
       } else if (arg.equals("--version")) {
         builder.setPrintVersion(true);
+        return builder;
+      } else if (arg.equals("--check")) {
+        checkCommandNotSet(command, builder, origin);
+        command = Command.CHECK;
+      } else if (arg.equals("--print-usage")) {
+        checkCommandNotSet(command, builder, origin);
+        command = Command.PRINTUSAGE;
+      } else if (arg.equals("--keep-rules")) {
+        checkCommandNotSet(command, builder, origin);
+        command = Command.KEEP_RULES;
+      } else if (arg.equals("--allowobfuscation")) {
+        allowObfuscation = true;
       } else if (arg.equals("--lib")) {
         addLibraryArgument(builder, origin, nextArg);
       } else if (arg.equals("--target")) {
         builder.addTargetFiles(Paths.get(nextArg));
       } else if (arg.equals("--source")) {
         builder.addSourceFiles(Paths.get(nextArg));
-      } else if (arg.equals("--format")) {
-        if (format != null) {
-          builder.error(new StringDiagnostic("--format specified multiple times"));
-        }
-        if (nextArg.equals("printuses")) {
-          format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE;
-        }
-        if (nextArg.equals("keep")) {
-          format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES;
-        }
-        if (nextArg.equals("keepallowobfuscation")) {
-          format = TraceReferencesFormattingConsumer.OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION;
-        }
-        if (format == null) {
-          builder.error(new StringDiagnostic("Unsupported format '" + nextArg + "'"));
-        }
       } else if (arg.equals("--output")) {
-        output = Paths.get(nextArg);
+        if (output != null) {
+          builder.error(new StringDiagnostic("Option '--output' passed multiple times.", origin));
+        } else {
+          output = Paths.get(nextArg);
+        }
       } else if (arg.startsWith("@")) {
         builder.error(new StringDiagnostic("Recursive @argfiles are not supported: ", origin));
       } else {
@@ -133,28 +178,50 @@
           i += argsConsumed;
           continue;
         }
-        builder.error(new StringDiagnostic("Unsupported argument '" + arg + "'"));
+        builder.error(new StringDiagnostic("Unsupported option '" + arg + "'", origin));
       }
     }
-    if (format == null) {
-      format = TraceReferencesFormattingConsumer.OutputFormat.PRINTUSAGE;
+
+    if (command == null) {
+      builder.error(
+          new StringDiagnostic(
+              "Missing command, specify one of 'check', '--print-usage' or '--keep-rules'",
+              origin));
+      return builder;
     }
+
+    if (command == Command.CHECK && output != null) {
+      builder.error(
+          new StringDiagnostic(
+              "Using '--output' requires command '--print-usage' or '--keep-rules'", origin));
+      return builder;
+    }
+
+    if (command != Command.KEEP_RULES && allowObfuscation) {
+      builder.error(
+          new StringDiagnostic(
+              "Using '--allowobfuscation' requires command '--keep-rules'", origin));
+      return builder;
+    }
+
     final Path finalOutput = output;
     builder.setConsumer(
-        new TraceReferencesFormattingConsumer(format) {
-          @Override
-          public void finished() {
-            PrintStream out = System.out;
-            if (finalOutput != null) {
-              try {
-                out = new PrintStream(Files.newOutputStream(finalOutput));
-              } catch (IOException e) {
-                builder.error(new ExceptionDiagnostic(e));
+        command == Command.CHECK
+            ? TraceReferencesConsumer.emptyConsumer()
+            : new TraceReferencesFormattingConsumer(command.toOutputFormat(allowObfuscation)) {
+              @Override
+              public void finished() {
+                PrintStream out = System.out;
+                if (finalOutput != null) {
+                  try {
+                    out = new PrintStream(Files.newOutputStream(finalOutput));
+                  } catch (IOException e) {
+                    builder.error(new ExceptionDiagnostic(e));
+                  }
+                }
+                out.print(get());
               }
-            }
-            out.print(get());
-          }
-        });
+            });
     return builder;
   }
 
diff --git a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
index 67167c4..1979123 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/TraceReferencesFormattingConsumer.java
@@ -8,7 +8,7 @@
 
 class TraceReferencesFormattingConsumer implements TraceReferencesConsumer {
 
-  public enum OutputFormat {
+  enum OutputFormat {
     /** Format used with the -printusage flag */
     PRINTUSAGE,
     /** Keep rules keeping each of the traced references */
diff --git a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
index 77c1e53..148ddd1 100644
--- a/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
+++ b/src/test/java/com/android/tools/r8/tracereferences/TraceReferencesCommandTest.java
@@ -20,13 +20,15 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.ZipUtils;
 import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
 import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -62,10 +64,21 @@
   @Test(expected = CompilationFailedException.class)
   public void emptyRunCommandLine() throws Throwable {
     DiagnosticsChecker.checkErrorsContains(
-        "No library specified",
+        "Missing command",
         handler -> {
           TraceReferences.run(
-              TraceReferencesCommand.parse(new String[] {""}, Origin.unknown(), handler).build());
+              TraceReferencesCommand.parse(new String[] {}, Origin.unknown(), handler).build());
+        });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void unsupportedCommandCommandLine() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "Missing command, specify one of 'check', '--print-usage' or '--keep-rules'",
+        handler -> {
+          TraceReferences.run(
+              TraceReferencesCommand.parse(new String[] {"--xxx"}, Origin.unknown(), handler)
+                  .build());
         });
   }
 
@@ -89,7 +102,7 @@
           TraceReferences.run(
               TraceReferencesCommand.parse(
                       new String[] {
-                        "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString()
+                        "--check", "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString()
                       },
                       Origin.unknown(),
                       handler)
@@ -98,24 +111,39 @@
   }
 
   @Test(expected = CompilationFailedException.class)
-  public void invalidFormatCommandLine() throws Throwable {
+  public void multipleCommandsSpecified() throws Throwable {
     DiagnosticsChecker.checkErrorsContains(
-        "Unsupported format 'xxx'",
+        "Multiple commands specified",
         handler -> {
           TraceReferences.run(
               TraceReferencesCommand.parse(
-                      new String[] {"--format", "xxx"}, Origin.unknown(), handler)
+                      new String[] {"--check", "--keep-rules"}, Origin.unknown(), handler)
                   .build());
         });
   }
 
   @Test(expected = CompilationFailedException.class)
-  public void missingFormatCommandLine() throws Throwable {
+  public void allowobfuscationWithoutKeepRule() throws Throwable {
     DiagnosticsChecker.checkErrorsContains(
-        "Missing parameter for --format",
+        "Using '--allowobfuscation' requires command '--keep-rules'",
         handler -> {
           TraceReferences.run(
-              TraceReferencesCommand.parse(new String[] {"--format"}, Origin.unknown(), handler)
+              TraceReferencesCommand.parse(
+                      new String[] {"--check", "--allowobfuscation"}, Origin.unknown(), handler)
+                  .build());
+        });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void allowobfuscationMultiple() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "No library specified",
+        handler -> {
+          TraceReferences.run(
+              TraceReferencesCommand.parse(
+                      new String[] {"--keep-rules", "--allowobfuscation", "--allowobfuscation"},
+                      Origin.unknown(),
+                      handler)
                   .build());
         });
   }
@@ -123,11 +151,23 @@
   @Test(expected = CompilationFailedException.class)
   public void multipleFormatsCommandLine() throws Throwable {
     DiagnosticsChecker.checkErrorsContains(
-        "--format specified multiple times",
+        "Using '--output' requires command '--print-usage' or '--keep-rules'",
         handler -> {
           TraceReferences.run(
               TraceReferencesCommand.parse(
-                      new String[] {"--format", "printuses", "--format", "keep"},
+                      new String[] {"--check", "--output", "xxx"}, Origin.unknown(), handler)
+                  .build());
+        });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void outputMultiple() throws Throwable {
+    DiagnosticsChecker.checkErrorsContains(
+        "Option '--output' passed multiple times",
+        handler -> {
+          TraceReferences.run(
+              TraceReferencesCommand.parse(
+                      new String[] {"--keep-rules", "--output", "xxx", "--output", "xxx"},
                       Origin.unknown(),
                       handler)
                   .build());
@@ -136,13 +176,13 @@
 
   private String formatName(OutputFormat format) {
     if (format == OutputFormat.PRINTUSAGE) {
-      return "printuses";
+      return "--print-usage";
     }
     if (format == OutputFormat.KEEP_RULES) {
-      return "keep";
+      return "--keep-rules";
     }
     assertSame(format, OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION);
-    return "keepallowobfuscation";
+    return "--keep-rules";
   }
 
   public void runAndCheckOutput(
@@ -179,17 +219,23 @@
       throw e;
     }
 
-    TraceReferences.run(
-        TraceReferencesCommand.parse(
-                new String[] {
-                  "--lib", ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
-                  "--target", targetJar.toString(),
-                  "--source", sourceJar.toString(),
-                  "--output", output.toString(),
-                  "--format", formatName(format)
-                },
-                Origin.unknown())
-            .build());
+    List<String> args = new ArrayList<>();
+    args.add(formatName(format));
+    if (format == OutputFormat.KEEP_RULES_WITH_ALLOWOBFUSCATION) {
+      args.add("--allowobfuscation");
+    }
+    args.addAll(
+        ImmutableList.of(
+            "--lib",
+            ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+            "--target",
+            targetJar.toString(),
+            "--source",
+            sourceJar.toString(),
+            "--output",
+            output.toString()));
+
+    TraceReferences.run(TraceReferencesCommand.parse(args, Origin.unknown()).build());
     assertEquals(expected, FileUtils.readTextFile(output, Charsets.UTF_8));
   }
 
@@ -202,6 +248,16 @@
     runAndCheckOutput(targetClasses, sourceClasses, format, expected, null);
   }
 
+  private Path zipWithTestClasses(Path zipFile, List<Class<?>> targetClasses) throws IOException {
+    return ZipBuilder.builder(zipFile)
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            targetClasses.stream()
+                .map(ToolHelper::getClassFileForTestClass)
+                .collect(Collectors.toList()))
+        .build();
+  }
+
   public void runAndCheckOutput(
       List<Class<?>> targetClasses,
       List<Class<?>> sourceClasses,
@@ -210,22 +266,8 @@
       Consumer<DiagnosticsChecker> diagnosticsCheckerConsumer)
       throws Throwable {
     Path dir = temp.newFolder().toPath();
-    Path targetJar =
-        ZipBuilder.builder(dir.resolve("target.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                targetClasses.stream()
-                    .map(ToolHelper::getClassFileForTestClass)
-                    .collect(Collectors.toList()))
-            .build();
-    Path sourceJar =
-        ZipBuilder.builder(dir.resolve("source.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                sourceClasses.stream()
-                    .map(ToolHelper::getClassFileForTestClass)
-                    .collect(Collectors.toList()))
-            .build();
+    Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), targetClasses);
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), sourceClasses);
     runAndCheckOutput(targetJar, sourceJar, format, expected, diagnosticsCheckerConsumer);
   }
 
@@ -276,6 +318,85 @@
                 "-keeppackagenames com.android.tools.r8.tracereferences")));
   }
 
+  @Test
+  public void test_noOutput() throws Throwable {
+    Path dir = temp.newFolder().toPath();
+    Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(Target.class));
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
+    PrintStream originalOut = System.out;
+    try {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      System.setOut(new PrintStream(baos));
+      TraceReferences.run(
+          TraceReferencesCommand.builder()
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
+              .addTargetFiles(targetJar)
+              .addSourceFiles(sourceJar)
+              .setConsumer(TraceReferencesConsumer.emptyConsumer())
+              .build());
+      assertEquals(0, baos.size());
+    } finally {
+      System.setOut(originalOut);
+    }
+
+    try {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      System.setOut(new PrintStream(baos));
+      TraceReferences.run(
+          TraceReferencesCommand.parse(
+                  new String[] {
+                    "--lib",
+                    ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+                    "--check",
+                    "--target",
+                    targetJar.toString(),
+                    "--source",
+                    sourceJar.toString(),
+                  },
+                  Origin.unknown())
+              .build());
+      assertEquals(0, baos.size());
+    } finally {
+      System.setOut(originalOut);
+    }
+  }
+
+  @Test
+  public void test_stdoutOutput() throws Throwable {
+    String expected =
+        StringUtils.lines(
+            ImmutableList.of(
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target",
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: void"
+                    + " method(int)",
+                "com.android.tools.r8.tracereferences.TraceReferencesCommandTest$Target: int"
+                    + " field"));
+    Path dir = temp.newFolder().toPath();
+    Path targetJar = zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(Target.class));
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
+    PrintStream originalOut = System.out;
+    try {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      System.setOut(new PrintStream(baos));
+      TraceReferences.run(
+          TraceReferencesCommand.parse(
+                  new String[] {
+                    "--lib",
+                    ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
+                    "--target",
+                    targetJar.toString(),
+                    "--source",
+                    sourceJar.toString(),
+                    "--print-usage",
+                  },
+                  Origin.unknown())
+              .build());
+      assertEquals(expected, baos.toString(Charsets.UTF_8.name()));
+    } finally {
+      System.setOut(originalOut);
+    }
+  }
+
   private void checkTargetMissing(DiagnosticsChecker diagnosticsChecker) {
     Field field;
     Method method;
@@ -344,32 +465,19 @@
   public void testMissingReference_errorToWarning() throws Throwable {
     Path dir = temp.newFolder().toPath();
     Path targetJar =
-        ZipBuilder.builder(dir.resolve("target.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                ToolHelper.getClassFileForTestClass(OtherTarget.class))
-            .build();
-    Path sourceJar =
-        ZipBuilder.builder(dir.resolve("source.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                ToolHelper.getClassFileForTestClass(Source.class))
-            .build();
-    Path output = dir.resolve("output.txt");
+        zipWithTestClasses(dir.resolve("target.jar"), ImmutableList.of(OtherTarget.class));
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
     DiagnosticsChecker diagnosticsChecker = new DiagnosticsChecker();
     TraceReferences.run(
         TraceReferencesCommand.parse(
                 new String[] {
+                  "--check",
                   "--lib",
                   ToolHelper.getAndroidJar(AndroidApiLevel.P).toString(),
                   "--target",
                   targetJar.toString(),
                   "--source",
                   sourceJar.toString(),
-                  "--output",
-                  output.toString(),
-                  "--format",
-                  formatName(OutputFormat.PRINTUSAGE),
                   "--map-diagnostics:MissingDefinitionsDiagnostic",
                   "error",
                   "warning"
@@ -407,12 +515,7 @@
             .addBytes(
                 DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
             .build();
-    Path sourceJar =
-        ZipBuilder.builder(dir.resolve("source.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                ToolHelper.getClassFileForTestClass(Source.class))
-            .build();
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
     try {
       runAndCheckOutput(
           targetJar,
@@ -436,16 +539,7 @@
             .addBytes(
                 DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
             .build();
-    Path sourceJar =
-        ZipBuilder.builder(dir.resolve("source.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                ToolHelper.getClassFileForTestClass(Source.class))
-            .build();
-    ZipUtils.zip(
-        sourceJar,
-        ToolHelper.getClassPathForTests(),
-        ToolHelper.getClassFileForTestClass(Source.class));
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
     try {
       runAndCheckOutput(
           targetJar,
@@ -473,12 +567,7 @@
             .addBytes(
                 DescriptorUtils.getPathFromJavaType(Target.class), getClassWithTargetRemoved())
             .build();
-    Path sourceJar =
-        ZipBuilder.builder(dir.resolve("source.jar"))
-            .addFilesRelative(
-                ToolHelper.getClassPathForTests(),
-                ToolHelper.getClassFileForTestClass(Source.class))
-            .build();
+    Path sourceJar = zipWithTestClasses(dir.resolve("source.jar"), ImmutableList.of(Source.class));
     try {
       runAndCheckOutput(
           targetJar,