Snap for 10453563 from a499c017dcd2d9ad1a17b263a5e518bf137e3228 to mainline-adservices-release

Change-Id: Id5cf57aee65d48a278642dcbee6483ddd8c48ea0
diff --git a/Android.mk b/Android.mk
index 51e3464..6bf77fe 100644
--- a/Android.mk
+++ b/Android.mk
@@ -31,14 +31,13 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
   caliper \
   caliper-gson \
-  guavalib \
+  guava \
   junit \
   testng \
   vogar-jsr305 \
   vogar-kxml-libcore-20110123
 
 LOCAL_ADDITIONAL_DEPENDENCIES := \
-  $(HOST_OUT_EXECUTABLES)/dx \
   $(HOST_OUT_EXECUTABLES)/d8 \
   $(HOST_OUT_JAVA_LIBRARIES)/desugar.jar
 
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8a6ec19
--- /dev/null
+++ b/README.md
@@ -0,0 +1,138 @@
+# Vogar
+
+Vogar is a generic code/test/benchmark runner tool for Android. It is
+primarily used to run libcore and art tests and benchmarks, however
+this tool can also run arbitrary Java files either on host or target
+device.
+
+Vogar supports multiple testing frameworks and configurations:
+
+ * Allows running JUnit tests, TestNG tests, jtreg tests, Caliper
+   benchmarks or executable Java classes. It supports running
+   fine-grained tests that can be specified with hash symbol, e.g.
+   "com.android.Test#test".
+
+ * Allows running tests and benchmarks using five available runtime
+   modes: `activity`, `app_process`, `device`, `host` or `jvm`.
+
+## Building and running
+
+First build it:
+
+* With a minimal `aosp/master-art` tree:
+```bash
+export SOONG_ALLOW_MISSING_DEPENDENCIES=true
+${ANDROID_BUILD_TOP}/art/tools/buildbot-build.sh --target
+```
+
+* With a full Android (AOSP) `aosp/master` tree:
+```bash
+m vogar
+```
+
+## Features
+
+Vogar supports running tests and/or benchmarks (called "actions" below in the document)
+in five different modes (specified with `--mode` option). An "action" is a `.java` file,
+directory or class names:
+
+ 1. Activity (`--mode=activity`)
+
+    Vogar runs given action in the context of an [`android.app.Activity`](https://developer.android.com/reference/android/app/Activity) on a device.
+
+ 2. App (`--mode=app_process`)
+
+    Vogar runs given action in an app_process runtime on a device or emulator.
+    Used in conjunction with the `--benchmark` option for running Caliper benchmarks.
+    This is required to benchmark any code relying on the android framework.
+
+    ```bash
+    Vogar --mode app_process --benchmark frameworks/base/core/tests/benchmarks/src/android/os/ParcelBenchmark.java
+    ```
+
+3. Device  (`--mode=device`)
+
+   Vogar runs given action in an ART runtime on a device or emulator.
+
+4. Host  (`--mode=host`)
+
+   Vogar runs in an ART runtime on the local machine built with any lunch combo.
+   Similar to "Device" mode but running local ART.
+
+5. JVM  (`--mode=jvm`)
+
+   Vogar runs given action in a Java VM on the local machine.
+
+Most frequently you will use either `--mode=device` or `--mode=host` mode.
+
+## Testing and debugging
+
+Vogar has unit test coverage around basic functionality. Most of the coverage
+is for [JUnit](https://junit.org/) and [TestNG](https://testng.org/) integration.
+
+### Building and running
+
+First, build tests with:
+```bash
+m vogar-tests
+```
+
+Run all tests using phony make target with:
+```bash
+m run-vogar-tests
+```
+
+Or run manually (if you want to specify a subset of all unit tests, for example):
+```bash
+java -cp ${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/vogar-tests.jar \
+      org.junit.runner.JUnitCore vogar.AllTests
+```
+
+## Architecture and implementation
+
+High level model of each Vogar run is:
+
+ 1. Parsing input options.
+ 2. Creating a list of `Task` objects that encapsulate various steps required.
+    These `Task` objects can depend on each other, and get executed only if all
+    dependencies are already executed.
+ 3. Executing tasks. It include assembling the code, dexing it, packing in
+    an activity/runnable dex jar, preparing the environment (host or device),
+    pushing all artifacts, and running it.
+
+### Classes overview
+
+The basic building block of Vogar execution is the `Task` class. There are several
+sub classes of `Task`, for example:
+
+ * `MkdirTask`
+ * `RmTask`
+ * `PrepareTarget`
+
+The `Target` class encapsulates the runtime environment, for example a
+remote device or the local host. There are four available environments:
+
+ * `AdbTarget` is used when `--mode=device` is set.
+
+    It makes sure device is connected, required directories are mount
+    properly, and all required files are synced to the device.
+
+ * `AdbChrootTarget` is used when `--mode=device --chroot=/data/chroot/`
+   are set.
+
+   Same as `AdbTarget` but relatively to a specified chroot directory
+   (instead of the whole system under the root directory on the device).
+
+ * `LocalTarget` is used when `--mode=host` or `--mode=jvm` are set.
+
+    Same as `AdbTarget` but runs on the host machine.
+
+ * `SshTarget` is used when `--ssh <host:port>` is set.
+
+    Same as `LocalTarget` but on a remote machine at the given address.
+
+After parsing command line options, Vogar builds a list of tasks which
+are put in a `TaskQueue`. They are executed using all available cores
+except when "Activity" mode is enabled -- in that case it is always one
+thread.
+
diff --git a/src/vogar/Dexer.java b/src/vogar/Dexer.java
index 0e38715..3bf50f0 100644
--- a/src/vogar/Dexer.java
+++ b/src/vogar/Dexer.java
@@ -20,6 +20,5 @@
  * Available tools for generating dex files
  */
 public enum Dexer {
-    DX,
     D8,
 }
diff --git a/src/vogar/Run.java b/src/vogar/Run.java
index ba36594..402b1c4 100644
--- a/src/vogar/Run.java
+++ b/src/vogar/Run.java
@@ -106,6 +106,8 @@
     public final boolean checkJni;
     public final boolean debugging;
     public final Integer sdkVersion;
+    public final boolean serialDexing;
+    public final boolean verboseDexStats;
 
     public Run(Vogar vogar, Toolchain toolchain, Console console, Mkdir mkdir,
             AndroidSdk androidSdk, Rm rm, Target target, File runnerDir)
@@ -186,6 +188,8 @@
         this.checkJni = vogar.checkJni;
         this.debugging = (vogar.debugPort != null) || vogar.debugApp;
         this.sdkVersion = vogar.sdkVersion;
+        this.serialDexing = vogar.serialDexing;
+        this.verboseDexStats = vogar.verboseDexStats;
     }
 
     private Mode createMode(ModeId modeId, Variant variant) {
diff --git a/src/vogar/Toolchain.java b/src/vogar/Toolchain.java
index b19d2cb..1d8189d 100644
--- a/src/vogar/Toolchain.java
+++ b/src/vogar/Toolchain.java
@@ -19,17 +19,13 @@
 import vogar.Dexer;
 
 public enum Toolchain {
-    // .dex: desugar + javac + dx
-    DX,
     // .dex: desugar + javac + d8
     D8,
     // .class: javac
     JAVAC;
 
     public Dexer getDexer() {
-        if (this == Toolchain.DX) {
-            return Dexer.DX;
-        } else if (this == Toolchain.D8) {
+        if (this == Toolchain.D8) {
             return Dexer.D8;
         }
         throw new IllegalStateException("No dexer for toolchain " + this);
diff --git a/src/vogar/Vogar.java b/src/vogar/Vogar.java
index 59a0616..7fba4aa 100644
--- a/src/vogar/Vogar.java
+++ b/src/vogar/Vogar.java
@@ -221,6 +221,12 @@
     @Option(names = {"--sdk-version"})
     Integer sdkVersion = 28;
 
+    @Option(names = {"--serial-dexing"})
+    boolean serialDexing = false;
+
+    @Option(names = {"--verbose-dex-stats"})
+    boolean verboseDexStats = false;
+
     @VisibleForTesting public Vogar() {}
 
     private void printUsage() {
@@ -254,7 +260,7 @@
         System.out.println("      DEFAULT: default (or N/A), X32: 32-bit, X64: 64-bit");
         System.out.println("      Default is: " + variant);
         System.out.println();
-        System.out.println("  --toolchain <DX|D8|JAVAC>: Which toolchain to use.");
+        System.out.println("  --toolchain <D8|JAVAC>: Which toolchain to use.");
         System.out.println("      Default depends on --mode value (currently "
                 + modeId.defaultToolchain() + " for --mode=" + modeId + ")");
         System.out.println();
@@ -327,6 +333,17 @@
         System.out.println();
         System.out.println("  --verbose: turn on persistent verbose output.");
         System.out.println();
+        System.out.println("  --serial-dexing: disallow Vogar spawn multiple simultaneous dex tasks");
+        System.out.println("      Enabling this is useful when there is a memory constraint;");
+        System.out.println("      and each dex task could easily consume around 1.5G of memory.");
+        System.out.println("      Default is: " + serialDexing);
+        System.out.println();
+        System.out.println("  --verbose-dex-stats: print verbose stats of used resources by dex tasks");
+        System.out.println("      Enabling this wraps each dex task in '/usr/bin/time -v' call");
+        System.out.println("      and adds its output to the stdout log. Useful to get a sense of");
+        System.out.println("      resource usage such as RSS memory, CPU usage and wall-clock time.");
+        System.out.println("      Default is: " + verboseDexStats);
+        System.out.println();
         System.out.println("  --check-jni: enable CheckJNI mode.");
         System.out.println("      See http://developer.android.com/training/articles/perf-jni.html.");
         System.out.println("      Default is: " + checkJni + ", but disabled for --benchmark.");
@@ -666,7 +683,7 @@
         AndroidSdk androidSdk = null;
         if (modeId.requiresAndroidSdk()) {
             androidSdk = AndroidSdk.createAndroidSdk(console, mkdir, modeId, language,
-                    !actionFiles.isEmpty());
+                    !actionFiles.isEmpty(), serialDexing, verboseDexStats);
         }
 
         if (runnerType == null) {
diff --git a/src/vogar/android/AndroidSdk.java b/src/vogar/android/AndroidSdk.java
index 15c936d..220e6a6 100644
--- a/src/vogar/android/AndroidSdk.java
+++ b/src/vogar/android/AndroidSdk.java
@@ -43,12 +43,11 @@
 
 
 /**
- * Android SDK commands such as adb, aapt and dx.
+ * Android SDK commands such as adb, aapt and d8.
  */
 public class AndroidSdk {
 
     private static final String D8_COMMAND_NAME = "d8";
-    private static final String DX_COMMAND_NAME = "dx";
     private static final String ARBITRARY_BUILD_TOOL_NAME = D8_COMMAND_NAME;
 
     private final Log log;
@@ -58,6 +57,8 @@
     private final String desugarJarPath;
     private final Md5Cache dexCache;
     private final Language language;
+    private final boolean serialDexing;
+    private final boolean verboseDexStats;
 
     public static Collection<File> defaultExpectations() {
         return Collections.singletonList(new File("libcore/expectations/knownfailures.txt"));
@@ -71,7 +72,7 @@
      */
     public static AndroidSdk createAndroidSdk(
             Log log, Mkdir mkdir, ModeId modeId, Language language,
-            boolean supportBuildFromSource) {
+            boolean supportBuildFromSource, boolean serialDexing, boolean verboseDexStats) {
         List<String> path = new Command.Builder(log).args("which", ARBITRARY_BUILD_TOOL_NAME)
                 .permitNonZeroExitStatus(true)
                 .execute();
@@ -99,7 +100,7 @@
          * Android build tree (target):
          *  ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/aapt
          *  ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/adb
-         *  ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/dx
+         *  ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/d8
          *  ${ANDROID_BUILD_TOP}/out/host/linux-x86/bin/desugar.jar
          *  ${ANDROID_BUILD_TOP}/out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates
          *      /classes.jar
@@ -220,7 +221,7 @@
         }
 
         return new AndroidSdk(log, mkdir, compilationClasspath, androidJarPath, desugarJarPath,
-                new HostFileCache(log, mkdir), language);
+                new HostFileCache(log, mkdir), language, serialDexing, verboseDexStats);
     }
 
     /** Logs jars that couldn't be found ands suggests a command for building them */
@@ -261,7 +262,8 @@
 
     @VisibleForTesting
     AndroidSdk(Log log, Mkdir mkdir, File[] compilationClasspath, String androidJarPath,
-               String desugarJarPath, HostFileCache hostFileCache, Language language) {
+               String desugarJarPath, HostFileCache hostFileCache, Language language,
+               boolean serialDexing, boolean verboseDexStats) {
         this.log = log;
         this.mkdir = mkdir;
         this.compilationClasspath = compilationClasspath;
@@ -269,6 +271,8 @@
         this.desugarJarPath = desugarJarPath;
         this.dexCache = new Md5Cache(log, "dex", hostFileCache);
         this.language = language;
+        this.serialDexing = serialDexing;
+        this.verboseDexStats = verboseDexStats;
     }
 
     // Goes up N levels in the filesystem hierarchy. Return the last file that exists if this goes
@@ -374,37 +378,28 @@
          * same package they're testing, even when that's a core
          * library package. If you're actually just using this tool to
          * execute arbitrary code, this has the unfortunate
-         * side-effect of preventing "dx" from protecting you from
+         * side-effect of preventing "d8" from protecting you from
          * yourself.
-         *
-         * Memory options pulled from build/core/definitions.mk to
-         * handle large dx input when building dex for APK.
          */
 
         Command.Builder builder = new Command.Builder(log);
+        if (verboseDexStats) {
+            builder.args("/usr/bin/time").args("-v");
+        }
         switch (dexer) {
-            case DX:
-                builder.args(DX_COMMAND_NAME);
-                builder.args("-JXms16M").args("-JXmx1536M");
-                builder.args("--min-sdk-version=" + language.getMinApiLevel());
-                if (multidex) {
-                    builder.args("--multi-dex");
-                }
-                builder.args("--dex")
-                    .args("--output=" + output)
-                    .args("--core-library")
-                    .args(filePaths);
-                builder.execute();
-                break;
             case D8:
                 List<String> sanitizedOutputFilePaths;
                 try {
-                    sanitizedOutputFilePaths = removeDexFilesForD8(filePaths);
+                    sanitizedOutputFilePaths = removeDexFilesForD8(filePaths, outputTempDir);
                 } catch (IOException e) {
                     throw new RuntimeException("Error while removing dex files from archive", e);
                 }
                 builder.args(D8_COMMAND_NAME);
                 builder.args("-JXms16M").args("-JXmx1536M");
+                builder.args("-JXX:+TieredCompilation").args("-JXX:TieredStopAtLevel=1");
+                builder.args("-JDcom.android.tools.r8.emitRecordAnnotationsInDex");
+                builder.args("-JDcom.android.tools.r8.emitPermittedSubclassesAnnotationsInDex");
+                builder.args("--thread-count").args("1");
 
                 // d8 will not allow compiling with a single dex file as the target, but if given
                 // a directory name will start its output in classes.dex but may overflow into
@@ -473,26 +468,35 @@
     }
 
     /**
+     * Generates a file path for a modified d8 input file.
+     * @param inputFile the d8 input file.
+     * @param outputDirectory the directory where the modified file should be written.
+     * @return the destination for the modified d8 input file.
+     */
+    private static File getModifiedD8Destination(File inputFile, File outputDirectory) {
+        String name = inputFile.getName();
+        int suffixStart = name.lastIndexOf('.');
+        if (suffixStart != -1) {
+            name = name.substring(0, suffixStart);
+        }
+        return new File(outputDirectory, name + "-d8.jar");
+    }
+
+    /**
       * Removes DEX files from an archive and preserves the rest.
       */
-    private List<String> removeDexFilesForD8(List<String> fileNames) throws IOException {
+    private List<String> removeDexFilesForD8(List<String> fileNames, File tempDir)
+            throws IOException {
         byte[] buffer = new byte[4096];
         List<String> processedFiles = new ArrayList<>(fileNames.size());
         for (String inputFileName : fileNames) {
-            String jarExtension = ".jar";
-            String outputFileName;
-            if (inputFileName.endsWith(jarExtension)) {
-                outputFileName =
-                    inputFileName.substring(0, inputFileName.length() - jarExtension.length())
-                    + "-d8" + jarExtension;
-            } else {
-                outputFileName = inputFileName + "-d8" + jarExtension;
-            }
+            File inputFile = new File(inputFileName);
+            File outputFile = getModifiedD8Destination(inputFile, tempDir);
             try (JarOutputStream outputJar =
-                    new JarOutputStream(new FileOutputStream(outputFileName))) {
+                    new JarOutputStream(new FileOutputStream(outputFile))) {
                 copyJarContentExcludingFiles(buffer, inputFileName, outputJar, ".dex");
             }
-            processedFiles.add(outputFileName);
+            processedFiles.add(outputFile.toString());
         }
         return processedFiles;
     }
diff --git a/src/vogar/android/DeviceRuntime.java b/src/vogar/android/DeviceRuntime.java
index 1c85793..2b5f01f 100644
--- a/src/vogar/android/DeviceRuntime.java
+++ b/src/vogar/android/DeviceRuntime.java
@@ -25,7 +25,6 @@
 import java.util.List;
 import java.util.Set;
 import vogar.Action;
-import vogar.Toolchain;
 import vogar.Variant;
 import vogar.Classpath;
 import vogar.Mode;
@@ -56,10 +55,18 @@
 
     @Override public Set<Task> installTasks() {
         Set<Task> result = new HashSet<Task>();
+        Task lastDexTask = null;
         // dex everything on the classpath and push it to the device.
         for (File classpathElement : run.classpath.getElements()) {
-            addCreateDexJarAndPushTasks(result, run.basenameOfJar(classpathElement),
+            Task currentDexTask = addCreateDexJarAndPushTasks(result, run.basenameOfJar(classpathElement),
                     classpathElement, null);
+            // If {@code serialDexing} is enabled, make each subsequent dex task
+            // depend on previous so any moment of time only one dexer (d8) instance is run
+            // simultaneously.
+            if (lastDexTask != null && run.serialDexing) {
+                currentDexTask.afterSuccess(lastDexTask);
+            }
+            lastDexTask = currentDexTask;
         }
         return result;
     }
@@ -131,7 +138,7 @@
         return result;
     }
 
-    private void addCreateDexJarAndPushTasks(
+    private Task addCreateDexJarAndPushTasks(
             Set<Task> tasks, String name, File jar, Action action) {
         File localDex = run.localDexFile(name);
         File localTempDir = run.localDir(name);
@@ -140,6 +147,7 @@
                 localTempDir);
         tasks.add(createDexJarTask);
         tasks.add(run.target.pushTask(localDex, deviceDex).afterSuccess(createDexJarTask));
+        return createDexJarTask;
     }
 
     private Task newCreateDexJarTask(Classpath classpath, File classpathElement, String name,
diff --git a/src/vogar/android/InstallApkTask.java b/src/vogar/android/InstallApkTask.java
index a62e85a..7186392 100644
--- a/src/vogar/android/InstallApkTask.java
+++ b/src/vogar/android/InstallApkTask.java
@@ -47,7 +47,7 @@
         // We can't just give dex multiple jars with conflicting class names
 
         // With that in mind, the APK packaging strategy is as follows:
-        // 1. dx to create a dex
+        // 1. d8 to create a dex
         // 2. aapt the dex to create apk
         // 3. sign the apk
         // 4. install the apk
diff --git a/src/vogar/commands/Command.java b/src/vogar/commands/Command.java
index 38fdf24..6c5c4b9 100644
--- a/src/vogar/commands/Command.java
+++ b/src/vogar/commands/Command.java
@@ -131,6 +131,7 @@
         int exitValue = process.waitFor();
         destroyed = true;
         if (exitValue != 0 && !permitNonZeroExitStatus) {
+            outputLines.add("Command exited with code: " + exitValue);
             throw new CommandFailedException(args, outputLines);
         }
 
diff --git a/src/vogar/tasks/BuildActionTask.java b/src/vogar/tasks/BuildActionTask.java
index fd4c43a..04605fd 100644
--- a/src/vogar/tasks/BuildActionTask.java
+++ b/src/vogar/tasks/BuildActionTask.java
@@ -17,6 +17,7 @@
 package vogar.tasks;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -90,7 +91,14 @@
         if (run.debugging) {
             javac.debug();
         }
-        if (javaFile != null) {
+        if (javaFile == null) {
+            if (JAVA_SOURCE_PATTERN.matcher(action.getTargetClass()).find()) {
+                // This is likely a missing source file rather than a class
+                // named something.java....
+                throw new FileNotFoundException("Unlikely classname, assuming missing source file: "
+                    + action.getTargetClass());
+            }
+        } else {
             if (!JAVA_SOURCE_PATTERN.matcher(javaFile.toString()).find()) {
                 throw new CommandFailedException(Collections.<String>emptyList(),
                         Collections.singletonList("Cannot compile: " + javaFile));
diff --git a/test/vogar/android/AbstractModeTest.java b/test/vogar/android/AbstractModeTest.java
index 225883c..f6f379a 100644
--- a/test/vogar/android/AbstractModeTest.java
+++ b/test/vogar/android/AbstractModeTest.java
@@ -73,7 +73,7 @@
         androidSdk = new AndroidSdk(console, mkdir,
                 new File[] {new File("classpath")}, "android.jar", "desugar.jar",
                 new HostFileCache(console, mkdir),
-                Language.CUR);
+                Language.CUR, false, false);
         Target target = createTarget();
 
         final Vogar vogar = new Vogar();
@@ -83,7 +83,7 @@
                     + ". Please check stdout.");
         }
 
-        run = new Run(vogar, Toolchain.DX, console, mkdir, androidSdk, new Rm(console), target,
+        run = new Run(vogar, Toolchain.D8, console, mkdir, androidSdk, new Rm(console), target,
                 new File("runner/dir"));
 
         classpath = new Classpath();
diff --git a/test/vogar/android/HostRuntimeLocalTargetTest.java b/test/vogar/android/HostRuntimeLocalTargetTest.java
index 45a147a..a6fbd02 100644
--- a/test/vogar/android/HostRuntimeLocalTargetTest.java
+++ b/test/vogar/android/HostRuntimeLocalTargetTest.java
@@ -87,11 +87,11 @@
                 + " -Xbootclasspath"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-oj-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-libart-hostdex.jar"
-                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-icu4j-hostdex.jar"
-                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/conscrypt-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/okhttp-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/bouncycastle-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/apache-xml-hostdex.jar"
+                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-icu4j-hostdex.jar"
+                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/conscrypt-hostdex.jar"
                 + " -Duser.language=en"
                 + " -Duser.region=US"
                 + " -Xcheck:jni"
@@ -149,11 +149,11 @@
                 + " -Xbootclasspath"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-oj-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-libart-hostdex.jar"
-                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-icu4j-hostdex.jar"
-                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/conscrypt-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/okhttp-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/bouncycastle-hostdex.jar"
                 + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/apache-xml-hostdex.jar"
+                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/core-icu4j-hostdex.jar"
+                + ":${ANDROID_BUILD_TOP}/out/host/linux-x86/framework/conscrypt-hostdex.jar"
                 + " -Duser.language=en"
                 + " -Duser.region=US"
                 + " -Xjnigreflimit:2000"
diff --git a/test/vogar/target/junit3/TestMethodWithParameterTest.java b/test/vogar/target/junit3/TestMethodWithParameterTest.java
index e345e17..28fd255 100644
--- a/test/vogar/target/junit3/TestMethodWithParameterTest.java
+++ b/test/vogar/target/junit3/TestMethodWithParameterTest.java
@@ -20,7 +20,7 @@
 
 public class TestMethodWithParameterTest extends TestCase {
 
-    @SuppressWarnings("unused")
+    @SuppressWarnings({"unused", "JUnit3TestNotRun"})
     public void testParameterized(int i) {
     }
 }
diff --git a/test/vogar/target/testng/ChangeDefaultLocaleTest.java b/test/vogar/target/testng/ChangeDefaultLocaleTest.java
index 1833ff2..1465f33 100644
--- a/test/vogar/target/testng/ChangeDefaultLocaleTest.java
+++ b/test/vogar/target/testng/ChangeDefaultLocaleTest.java
@@ -20,7 +20,6 @@
 
 import java.util.Locale;
 import org.testng.annotations.Test;
-;
 
 /**
  * Verify that the locale is reset to Locale.US before/after each test is run.
diff --git a/test/vogar/target/testng/TestNgAnnotationsClass.java b/test/vogar/target/testng/TestNgAnnotationsClass.java
new file mode 100644
index 0000000..3a28151
--- /dev/null
+++ b/test/vogar/target/testng/TestNgAnnotationsClass.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package vogar.target.testng;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class TestNgAnnotationsClass {
+    @BeforeClass
+    public void beforeClass() {
+        System.out.println("@BeforeClass");
+    }
+
+    @AfterClass
+    public void afterClass() {
+        System.out.println("@AfterClass");
+    }
+
+    @Test
+    public void test1() {
+    }
+
+    @Test
+    public void test2() {
+    }
+
+    @Test
+    public void test3() {
+    }
+}
diff --git a/test/vogar/target/testng/TestNgAnnotationsMethod.java b/test/vogar/target/testng/TestNgAnnotationsMethod.java
new file mode 100644
index 0000000..12840c8
--- /dev/null
+++ b/test/vogar/target/testng/TestNgAnnotationsMethod.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package vogar.target.testng;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.AfterMethod;
+
+public class TestNgAnnotationsMethod {
+    @BeforeMethod
+    public void beforeMethod() {
+        System.out.println("@BeforeMethod");
+    }
+
+    @AfterMethod
+    public void afterMethod() {
+        System.out.println("@AfterMethod");
+    }
+
+    @Test
+    public void test1() {
+    }
+
+    @Test
+    public void test2() {
+    }
+
+    @Test
+    public void test3() {
+    }
+}
diff --git a/test/vogar/target/testng/TestRunnerTestNgTest.java b/test/vogar/target/testng/TestRunnerTestNgTest.java
index 060244e..c1f1cb1 100644
--- a/test/vogar/target/testng/TestRunnerTestNgTest.java
+++ b/test/vogar/target/testng/TestRunnerTestNgTest.java
@@ -77,4 +77,66 @@
                 .completedNormally();
     }
 
+    @TestRunnerProperties(testClass = TestNgAnnotationsMethod.class)
+    @Test
+    public void testRunner_TestNgAnnotationsMethod_OneMethod() throws Exception {
+        String[] args = {"test1"};
+        TestRunner runner = testRunnerRule.createTestRunner(args);
+        runner.run();
+
+        expectedResults()
+                .text("@BeforeMethod\n")
+                .success("test1")
+                .text("@AfterMethod\n")
+                .completedNormally();
+    }
+
+    @TestRunnerProperties(testClass = TestNgAnnotationsMethod.class)
+    @Test
+    public void testRunner_TestNgAnnotationsMethod_AllMethods() throws Exception {
+        TestRunner runner = testRunnerRule.createTestRunner();
+        runner.run();
+
+        expectedResults()
+                .text("@BeforeMethod\n")
+                .success("test1")
+                .text("@AfterMethod\n")
+                .text("@BeforeMethod\n")
+                .success("test2")
+                .text("@AfterMethod\n")
+                .text("@BeforeMethod\n")
+                .success("test3")
+                .text("@AfterMethod\n")
+                .completedNormally();
+    }
+
+    @TestRunnerProperties(testClass = TestNgAnnotationsClass.class)
+    @Test
+    public void testRunner_TestNgAnnotationsClass_OneMethod() throws Exception {
+        String[] args = {"test1"};
+        TestRunner runner = testRunnerRule.createTestRunner(args);
+        runner.run();
+
+        expectedResults()
+                .text("@BeforeClass\n")
+                .success("test1")
+                .text("@AfterClass\n")
+                .completedNormally();
+    }
+
+    @TestRunnerProperties(testClass = TestNgAnnotationsClass.class)
+    @Test
+    public void testRunner_TestNgAnnotationsClass_AllMethods() throws Exception {
+        TestRunner runner = testRunnerRule.createTestRunner();
+        runner.run();
+
+        expectedResults()
+                .text("@BeforeClass\n")
+                .success("test1")
+                .success("test2")
+                .success("test3")
+                .text("@AfterClass\n")
+                .completedNormally();
+    }
+
 }