Merge "Replace outputs to stdout/err by Diagnostics"
diff --git a/build.gradle b/build.gradle
index e10d9e0..1ad9b98 100644
--- a/build.gradle
+++ b/build.gradle
@@ -140,6 +140,7 @@
     testCompile group: 'org.smali', name: 'smali', version: '2.2b4'
     testCompile files('third_party/jasmin/jasmin-2.4.jar')
     testCompile files('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar')
+    testCompile files('third_party/ddmlib/ddmlib.jar')
     jctfCommonCompile 'junit:junit:4.12'
     jctfTestsCompile 'junit:junit:4.12'
     jctfTestsCompile sourceSets.jctfCommon.output
@@ -202,6 +203,7 @@
                 "kotlin",
                 "android_cts_baseline",
                 "shadow",
+                "ddmlib",
         ],
         // All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
         // container on other platforms where supported.
diff --git a/scripts/test-exit-status.sh b/scripts/test-exit-status.sh
new file mode 100644
index 0000000..2fa99d5
--- /dev/null
+++ b/scripts/test-exit-status.sh
@@ -0,0 +1,8 @@
+# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+UUID=$1
+shift
+eval "$@"
+echo $? > /data/r8-tests-$UUID/exitStatus
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index b1bdfde..47bd02e 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -9,6 +9,7 @@
  * Android API level description
  */
 public enum AndroidApiLevel {
+  LATEST(-1),
   P(27),
   O(26),
   N_MR1(25),
@@ -76,6 +77,9 @@
 
   public static AndroidApiLevel getAndroidApiLevel(int apiLevel) {
     switch (apiLevel) {
+      case 0:
+        // 0 is not supported, it should not happen
+        throw new Unreachable();
       case 1:
         return B;
       case 2:
@@ -131,7 +135,7 @@
       case 27:
         return P;
       default:
-        throw new Unreachable();
+        return LATEST;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index e45a0dc..c9fa818 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -50,6 +50,7 @@
     switch (androidApiLevel) {
       case P:
         return DexVersion.V39;
+      case LATEST:
       case O:
         return DexVersion.V38;
       case N_MR1:
diff --git a/src/test/examples/filledarray/FilledArray.java b/src/test/examples/filledarray/FilledArray.java
index 46c437c..4e0c652 100644
--- a/src/test/examples/filledarray/FilledArray.java
+++ b/src/test/examples/filledarray/FilledArray.java
@@ -10,8 +10,7 @@
   private static byte[] bytes = new byte[] {
       0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, -19, -20, -96,
       Byte.MAX_VALUE, Byte.MIN_VALUE };
-  private static char[] chars = new char[] {
-      Character.MAX_VALUE, 'a', 'b', 'c', 'd', Character.MIN_VALUE };
+  private static char[] chars = new char[] {'a', 'b', 'c', 'd'};
   private static int[] ints = new int[] { Integer.MAX_VALUE, 0, -42, 42, Integer.MIN_VALUE };
   private static short[] shorts = new short[] { Short.MAX_VALUE, 0, -42, 42, Short.MIN_VALUE };
   private static long[] longs = new long[] {
diff --git a/src/test/java/com/android/tools/r8/DeviceRunner.java b/src/test/java/com/android/tools/r8/DeviceRunner.java
new file mode 100644
index 0000000..fc91032
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DeviceRunner.java
@@ -0,0 +1,532 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.base.Joiner;
+
+import com.android.ddmlib.AdbCommandRejectedException;
+import com.android.ddmlib.AndroidDebugBridge;
+import com.android.ddmlib.FileListingService;
+import com.android.ddmlib.IDevice;
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.ShellCommandUnresponsiveException;
+import com.android.ddmlib.SyncException;
+import com.android.ddmlib.TimeoutException;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * This class is used to run tests on devices (or emulators)
+ * using ddmlib.
+ */
+public class DeviceRunner {
+
+  private static final boolean VERBOSE = false;
+  private static final long ADB_CONNECTION_TIMEOUT = 5000;
+  private static final long ADB_WAIT_STEP = ADB_CONNECTION_TIMEOUT / 10;
+  private static final String TEST_SCRIPT_NAME = "test-exit-status.sh";
+  private static final File TEST_SCRIPT_FILE =
+      new File(System.getProperty("user.dir"), "scripts/" + TEST_SCRIPT_NAME);
+  private static final char PATH_SEPARATOR_CHAR = ':';
+  private static final String RUNTIME_NAME = "ART";
+
+  private List<String> vmOptions = Collections.emptyList();
+  private Map<String, String> systemProperties = Collections.emptyMap();
+  private List<File> classpath = Collections.emptyList();
+  private List<File> bootClasspath = Collections.emptyList();
+  private String mainClass;
+  private List<String> programArguments = Collections.emptyList();
+  private OutputStream outRedirectStream = new ByteArrayOutputStream();
+
+  private ShellOutputReceiver hostOutput = new ShellOutputReceiver();
+
+  public static class DeviceRunnerConfigurationException extends Exception {
+
+    public DeviceRunnerConfigurationException(String message) {
+      super(message);
+    }
+  }
+
+  private static class ShellOutputReceiver implements IShellOutputReceiver {
+
+    private final PrintStream out;
+
+    public ShellOutputReceiver() {
+      this.out = System.out;
+    }
+
+    public ShellOutputReceiver(OutputStream out) {
+      this.out = new PrintStream(out);
+    }
+
+    @Override
+    public void addOutput(byte[] data, int offset, int length) {
+      out.print(new String(Arrays.copyOfRange(data, offset, offset + length)));
+    }
+
+    @Override
+    public void flush() {
+    }
+
+    @Override
+    public boolean isCancelled() {
+      return false;
+    }
+  }
+
+  private static class ShellOutputToStringReceiver implements IShellOutputReceiver {
+
+    StringBuffer outBuffer = new StringBuffer();
+
+    @Override
+    public void addOutput(byte[] data, int offset, int length) {
+      outBuffer.append(new String(Arrays.copyOfRange(data, offset, offset + length)));
+    }
+
+    @Override
+    public void flush() {
+    }
+
+    @Override
+    public boolean isCancelled() {
+      return false;
+    }
+
+    public String getOutput() {
+      return outBuffer.toString();
+    }
+  }
+
+  public DeviceRunner() {
+    try {
+      AndroidDebugBridge.init(/* clientSupport */ false);
+    } catch (IllegalStateException ex) {
+      // ADB was already initialized, we're fine, so just ignore.
+    }
+  }
+
+  public DeviceRunner setVmOptions(List<String> vmOptions) {
+    this.vmOptions = vmOptions;
+    return this;
+  }
+
+  public DeviceRunner setSystemProperties(Map<String, String> systemProperties) {
+    this.systemProperties = systemProperties;
+    return this;
+  }
+
+  public DeviceRunner setClasspath(List<File> classpath) {
+    this.classpath = classpath;
+    return this;
+  }
+
+  public DeviceRunner setBootClasspath(List<File> bootClasspath) {
+    this.bootClasspath = bootClasspath;
+    return this;
+  }
+
+  public DeviceRunner setMainClass(String mainClass) {
+    this.mainClass = mainClass;
+    return this;
+  }
+
+  public DeviceRunner setProgramArguments(List<String> programArguments) {
+    this.programArguments = programArguments;
+    return this;
+  }
+
+  public DeviceRunner setOutputStream(OutputStream outputStream) {
+    outRedirectStream = outputStream;
+    return this;
+  }
+
+  public ToolHelper.ProcessResult run() throws DeviceRunnerConfigurationException, IOException {
+
+    AndroidDebugBridge adb = initializeAdb();
+
+    IDevice[] connectedDevices = adb.getDevices();
+    if (connectedDevices.length == 0) {
+      throw new DeviceRunnerConfigurationException("No device found");
+    }
+
+    if (connectedDevices.length != 1) {
+      throw new DeviceRunnerConfigurationException(
+          "Running tests on more than one device is not yet supported. "
+              + "Currently connected devices: ["
+              + StringUtils.join(Arrays.asList(connectedDevices), ",") + "]");
+    }
+
+    int exitStatus = -1;
+    String uuid = java.util.UUID.randomUUID().toString();
+    IDevice device = connectedDevices[0];
+    try {
+      checkDeviceRuntime(device);
+
+      log("Running on device: " + device.getName());
+
+      ensureAdbRoot(device);
+
+      // Remove trailing '\n' returned by emulator
+      File testsRootDirFile = new File(device.getMountPoint(IDevice.MNT_DATA).replace("\n", ""),
+          "r8-tests-" + uuid);
+      String testsRootDir = convertToTargetPath(testsRootDirFile);
+
+      String testScriptPathOnTarget = convertToTargetPath(
+          new File(testsRootDirFile, TEST_SCRIPT_NAME));
+
+      ClasspathPair destFilePaths = installTestFiles(device, testsRootDirFile,
+          testScriptPathOnTarget);
+
+      File rootDir = new File(device.getMountPoint(IDevice.MNT_ROOT).replace("\n", ""));
+
+      // Bug : exit code return by adb shell is wrong (always 0)
+      // https://code.google.com/p/android/issues/detail?id=3254
+      // Use go team hack to work this around
+      // https://code.google.com/p/go/source/browse/misc/arm/a
+      executeShellCommand(testScriptPathOnTarget + ' ' + uuid + ' ' + Joiner.on(' ').join(
+          buildCommandLine(rootDir, testsRootDir, destFilePaths.bootclasspath,
+              destFilePaths.classpath)), device, new ShellOutputReceiver(outRedirectStream),
+          /* maxTimeToOutputResponse = */10000);
+      exitStatus = getExitStatus(device, testsRootDir);
+      log("Exit status: " + exitStatus);
+
+      if (exitStatus != 0) {
+        System.err.println("Execution failed on device '" + device.getName() + "'");
+      }
+
+    } catch (IOException t) {
+      System.err.println("Error with device '" + device.getName() + "': " + t.getMessage());
+      t.printStackTrace();
+      throw t;
+    } finally {
+      deleteTestFiles(device, uuid);
+    }
+
+    // Convert target line separator to whatever host is for output comparison-based tests
+    String outputAsString = outRedirectStream.toString()
+        .replace("\n", ToolHelper.LINE_SEPARATOR);
+
+    return new ToolHelper.ProcessResult(exitStatus, outputAsString, "");
+  }
+
+  private AndroidDebugBridge initializeAdb() throws DeviceRunnerConfigurationException {
+    AndroidDebugBridge adb = AndroidDebugBridge.createBridge(getAdbLocation(), false);
+
+    long start = System.currentTimeMillis();
+
+    log("Initializing adb...");
+
+    while (!isAdbInitialized(adb)) {
+      long timeLeft = start + ADB_CONNECTION_TIMEOUT - System.currentTimeMillis();
+      if (timeLeft <= 0) {
+        break;
+      }
+      try {
+        Thread.sleep(ADB_WAIT_STEP);
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        throw new AssertionError(e);
+      }
+    }
+
+    if (!isAdbInitialized(adb)) {
+      String userDefinedPathToSdk = System.getProperty("ANDROID_SDK_HOME");
+      if (userDefinedPathToSdk != null) {
+        throw new DeviceRunnerConfigurationException(
+            "Adb not found. Check SDK location '"
+                + userDefinedPathToSdk
+                + "'");
+      } else {
+        throw new DeviceRunnerConfigurationException(
+            "Adb not found. Set either PATH or ANDROID_SDK_HOME environment variable");
+      }
+    }
+
+    log("Done");
+    return adb;
+  }
+
+  private static class ClasspathPair {
+    public String[] bootclasspath;
+    public String[] classpath;
+
+    ClasspathPair(String[] bootclasspath, String[] classpath) {
+      this.bootclasspath = bootclasspath;
+      this.classpath = classpath;
+    }
+  }
+
+  /**
+   * @return path of classpath and bootclasspath files on device
+   */
+  private ClasspathPair installTestFiles(IDevice device, File testsRootDirFile,
+      String testScriptPathOnTarget) throws IOException {
+
+    String testsRootDir = convertToTargetPath(testsRootDirFile);
+
+    String[] desFilePaths = new String[classpath.size()];
+    String[] destFileBootCpPaths = new String[bootClasspath.size()];
+
+    executeShellCommand("mkdir " + testsRootDir, device);
+    executeShellCommand(
+        "rm " + testsRootDir + FileListingService.FILE_SEPARATOR + "*", device);
+    executePushCommand(TEST_SCRIPT_FILE.getAbsolutePath(), testScriptPathOnTarget, device);
+    executeShellCommand("chmod 777 " + testScriptPathOnTarget, device);
+
+    int i = 0;
+    for (File f : bootClasspath) {
+      destFileBootCpPaths[i] = convertToTargetPath(
+          new File(testsRootDirFile, "f" + i + "_" + f.getName()));
+      executePushCommand(f.getAbsolutePath(), destFileBootCpPaths[i], device);
+      i++;
+    }
+    i = 0;
+    for (File f : classpath) {
+      desFilePaths[i] = convertToTargetPath(
+          new File(testsRootDirFile, "f" + i + "_" + f.getName()));
+      executePushCommand(f.getAbsolutePath(), desFilePaths[i], device);
+      i++;
+    }
+    return new ClasspathPair(destFileBootCpPaths, desFilePaths);
+  }
+
+  private int getExitStatus(IDevice device, String testsRootDir) throws IOException {
+    File exitStatusFile = createTempFile("exitStatus", "");
+    executePullCommand(
+        testsRootDir + "/exitStatus", exitStatusFile.getAbsolutePath(), device);
+
+    try (BufferedReader br = new BufferedReader(new FileReader(exitStatusFile))) {
+      String readLine = br.readLine();
+      if (readLine == null) {
+        throw new FileNotFoundException("Exit status not found at " + exitStatusFile.getPath());
+      }
+      return Integer.parseInt(readLine);
+    }
+  }
+
+  private void executeShellCommand(
+      String command,
+      IDevice device,
+      ShellOutputReceiver hostOutput,
+      int maxTimeToOutputResponse) throws IOException {
+    log("adb -s " + device.getSerialNumber() + " shell " + command);
+    try {
+      if (maxTimeToOutputResponse != -1) {
+        device.executeShellCommand(command, hostOutput, maxTimeToOutputResponse);
+      } else {
+        device.executeShellCommand(command, hostOutput);
+      }
+    } catch (IOException
+        | ShellCommandUnresponsiveException
+        | TimeoutException
+        | AdbCommandRejectedException e) {
+      throw new IOException(
+          "Failed to execute shell command: '" + command + "'", e);
+    }
+  }
+
+  private void executeShellCommand(String command, IDevice device)
+      throws IOException {
+    executeShellCommand(command, device, hostOutput, -1);
+  }
+
+  private void executePushCommand(
+      String srcFile, String destFile, IDevice device) throws IOException {
+    log("adb -s " + device.getSerialNumber() + " push " + srcFile + " " + destFile);
+    try {
+      device.pushFile(srcFile, destFile);
+    } catch (IOException | AdbCommandRejectedException | TimeoutException | SyncException e) {
+      throw new IOException(
+          "Unable to push file '" + srcFile + "' on device into '" + destFile + "'", e);
+    }
+  }
+
+  private void executePullCommand(
+      String srcFile, String destFile, IDevice device) throws IOException {
+    log("adb -s " + device.getSerialNumber() + " pull " + srcFile + " " + destFile);
+    try {
+      device.pullFile(srcFile, destFile);
+    } catch (IOException | AdbCommandRejectedException | TimeoutException | SyncException e) {
+      throw new IOException(
+          "Unable to pull file '" + srcFile + "' from device into '" + destFile + "'", e);
+    }
+  }
+
+  private boolean isAdbInitialized(AndroidDebugBridge adb) {
+    return adb.isConnected() && adb.hasInitialDeviceList();
+  }
+
+  private String getAdbLocation() {
+    String adbLocation = "adb";
+    String userSpecifiedSdkLocation = System.getenv("ANDROID_SDK_HOME");
+    if (userSpecifiedSdkLocation != null) {
+      adbLocation =
+          userSpecifiedSdkLocation
+              + File.separatorChar
+              + "platform-tools"
+              + File.separatorChar
+              + "adb";
+    }
+    return adbLocation;
+  }
+
+
+  private void ensureAdbRoot(IDevice device) throws IOException {
+    boolean isRoot;
+    try {
+      isRoot = device.isRoot();
+    } catch (TimeoutException
+        | AdbCommandRejectedException
+        | IOException
+        | ShellCommandUnresponsiveException e) {
+      throw new IOException(
+          "Cannot fetch root status for device '"
+              + device.getName()
+              + "("
+              + device.getSerialNumber()
+              + ")"
+              + "': "
+              + e.getMessage(),
+          e);
+    }
+
+    int numberOfTries = 0;
+    while (!isRoot && numberOfTries < 5) {
+      try {
+        isRoot = device.root();
+      } catch (TimeoutException
+          | AdbCommandRejectedException
+          | IOException
+          | ShellCommandUnresponsiveException e1) {
+        // root() seems to throw an IOException: EOF, and it tends
+        // to make the subsequent call to isRoot() fail with
+        // AdbCommandRejectedException: device offline, until adbd is
+        // restarted as root.
+      } finally {
+        try {
+          Thread.sleep(1000);
+        } catch (InterruptedException e1) {
+          Thread.currentThread().interrupt();
+        }
+      }
+      numberOfTries++;
+    }
+
+    if (!isRoot) {
+      throw new IOException(
+          "Cannot switch to root on device '"
+              + device.getName()
+              + "("
+              + device.getSerialNumber()
+              + ")"
+              + "'");
+    }
+  }
+
+  private void checkDeviceRuntime(IDevice device)
+      throws DeviceRunnerConfigurationException, IOException {
+    ShellOutputToStringReceiver outputToString = new ShellOutputToStringReceiver();
+    try {
+      device.executeShellCommand("dalvikvm -showversion", outputToString);
+      if (!outputToString.getOutput().contains(RUNTIME_NAME)) {
+        throw new DeviceRunnerConfigurationException(
+            "The plugged device does not run the required runtime: '" + RUNTIME_NAME + "'");
+      }
+    } catch (TimeoutException
+        | AdbCommandRejectedException
+        | ShellCommandUnresponsiveException
+        | IOException e) {
+      throw new IOException("Could not check device runtime", e);
+    }
+  }
+
+  private String convertToTargetPath(File file) {
+    String path = file.getPath();
+    Path root = file.toPath().getRoot();
+    if (root != null) {
+      path = path.replace(root.toString(), FileListingService.FILE_SEPARATOR);
+    }
+    return path.replace(File.separator, FileListingService.FILE_SEPARATOR);
+  }
+
+  private static File createTempFile(String prefix, String suffix) throws IOException {
+    File tmp = File.createTempFile("r8-tests-" + prefix, suffix);
+    tmp.deleteOnExit();
+    return tmp;
+  }
+
+  private void deleteTestFiles(IDevice device, String uuid) throws IOException {
+    String deleteCommand = "find data -name '*r8-tests-" + uuid + "*' -exec rm -rf {} +";
+    log("adb -s " + device.getSerialNumber() + " shell " + deleteCommand);
+    try {
+      device.executeShellCommand(deleteCommand, hostOutput);
+    } catch (TimeoutException
+        | AdbCommandRejectedException
+        | ShellCommandUnresponsiveException
+        | IOException e) {
+      throw new IOException("Error while deleting test file on device", e);
+    }
+  }
+
+  private List<String> buildCommandLine(File rootDir, String testRootDir,
+      String[] bootClasspathFiles, String[] classpathFiles) {
+    List<String> result = new ArrayList<>();
+
+    result.add(convertToTargetPath(new File(rootDir, "/bin/dalvikvm")));
+
+    for (String option : vmOptions) {
+      result.add(option);
+    }
+    for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
+      StringBuilder builder = new StringBuilder("-D");
+      builder.append(entry.getKey());
+      builder.append("=");
+      builder.append(entry.getValue());
+      result.add(builder.toString());
+    }
+
+    if (classpathFiles.length > 0) {
+      result.add("-cp");
+      result.add(Joiner.on(PATH_SEPARATOR_CHAR).join(
+          Arrays.asList(classpathFiles).stream()
+              .map(filePath -> convertToTargetPath(new File(filePath)))
+              .collect(Collectors.toList())));
+    }
+    if (bootClasspathFiles.length > 0) {
+      result.add("-Xbootclasspath:" + Joiner.on(PATH_SEPARATOR_CHAR).join(
+          Arrays.asList(bootClasspathFiles).stream()
+              .map(filePath -> convertToTargetPath(new File(filePath)))
+              .collect(Collectors.toList())));
+    }
+
+    if (mainClass != null) {
+      result.add(mainClass);
+    }
+    for (String argument : programArguments) {
+      result.add(argument);
+    }
+    return result;
+  }
+
+  private void log(String message) {
+    if (VERBOSE) {
+      System.out.println(message);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index c58fc2b..e262359 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.R8RunArtTestsTest.DexTool;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.Multimap;
 import java.util.Collection;
@@ -41,11 +42,11 @@
           // java.lang.AssertionError: Expected exception: java.lang.NumberFormatException
 
           .put("lang.StringBuffer.insertILjava_lang_Object.StringBuffer_insert_A01",
-              match(runtimes(DexVm.ART_DEFAULT)))
+              match(runtimes(Version.DEFAULT)))
           // 1) t01
           // java.lang.StringIndexOutOfBoundsException: length=21; regionStart=0; regionLength=42
 
-          .put("lang.StringBuffer.serialization.StringBuffer_serialization_A01", any())
+          .put("lang.StringBuffer.serialization.StringBuffer_serialization_A01",  any())
           // 1) t01
           // java.lang.AssertionError: Unable to configure default providers
           // 2) t02
@@ -67,12 +68,12 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/NumberFormatException/serialization/NumberFormatException_serialization_A01.golden.0.ser
 
           .put("lang.StrictMath.roundF.StrictMath_round_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) t01
           // java.lang.AssertionError: Wrong result produced for argument: 0.49999997 expected:<1.0> but was:<0.0>
 
           .put("lang.StrictMath.roundD.StrictMath_round_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) t01
           // java.lang.AssertionError: Wrong result produced for argument: 0.49999999999999994 expected:<1.0> but was:<0.0>
 
@@ -126,7 +127,7 @@
 
           .put(
               "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1)))
           // 1) t01
           // java.lang.OutOfMemoryError: pthread_create (-8589934591GB stack) failed: Resource temporarily unavailable
 
@@ -154,7 +155,7 @@
           // java.lang.AssertionError: Expected exception: java.lang.IllegalThreadStateException
 
           .put("lang.Thread.getAllStackTraces.Thread_getAllStackTraces_A01",
-              match(runtimes(DexVm.ART_7_0_0)))
+              match(runtimes(Version.V7_0_0)))
           // 1) t01
           // java.lang.AssertionError
 
@@ -196,7 +197,7 @@
           // java.lang.UnsupportedOperationException
 
           .put("lang.Thread.getContextClassLoader.Thread_getContextClassLoader_A03",
-              match(runtimes(DexVm.ART_7_0_0)))
+              match(runtimes(Version.V7_0_0)))
           // 1) t01
           // java.lang.AssertionError: improper ClassLoader expected same:<null> was not:<dalvik.system.PathClassLoader[DexPathList[[dex file "/tmp/junit7794202178392390143/classes.dex"],nativeLibraryDirectories=[r8/tools/linux/art/bin/../lib, r8/tools/linux/art/bin/../lib]]]>
 
@@ -956,7 +957,7 @@
 
           .put(
               "lang.ClassLoader.defineClassLjava_lang_StringLjava_nio_ByteBufferLjava_security_ProtectionDomain.ClassLoader_defineClass_A07",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) t01
           // java.lang.Exception: Unexpected exception, expected<java.lang.NullPointerException> but was<java.lang.UnsupportedOperationException>
           // Caused by: java.lang.UnsupportedOperationException: can't load this type of class file
@@ -1733,22 +1734,22 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/Byte/serialization/Byte_serialization_A01.golden.0.ser
 
           .put("lang.Byte.parseByteLjava_lang_StringI.Byte_parseByte_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Parsed Byte instance from string:+1 radix:10
 
           .put("lang.Byte.valueOfLjava_lang_StringI.Byte_valueOf_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Parsed Byte instance from string:+1 radix:10
 
           .put("lang.Byte.valueOfLjava_lang_String.Byte_ValueOf_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t02
           // java.lang.AssertionError: Parsed Byte instance from string:+1
 
           .put("lang.Byte.decodeLjava_lang_String.Byte_decode_A04",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Decoded Byte instance from string:+1
 
@@ -1765,12 +1766,14 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/ClassCastException/serialization/ClassCastException_serialization_A01.golden.0.ser
 
           .put("lang.Byte.ConstructorLjava_lang_String.Byte_Constructor_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
+                  DexVm.Version.V5_1_1)))
           // 1) t02
           // java.lang.AssertionError: Parsed Byte instance from string:+1
 
           .put("lang.Byte.parseByteLjava_lang_String.Byte_parseByte_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
+                  DexVm.Version.V5_1_1)))
           // 1) t02
           // java.lang.AssertionError: Parsed Byte instance from string:+1
 
@@ -1840,7 +1843,7 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/Boolean/serialization/Boolean_serialization_A01.golden.0.ser
 
           .put("lang.Integer.valueOfLjava_lang_StringI.Integer_valueOf_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, Version.V6_0_1, DexVm.Version.V5_1_1)))
           // 1) t07
           // java.lang.AssertionError: NumberFormatException expected for input: +1 and radix: 10
 
@@ -1849,52 +1852,52 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/Integer/serialization/Integer_serialization_A01.golden.0.ser
 
           .put("lang.Integer.parseIntLjava_lang_String.Integer_parseInt_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
           // 1) t06
           // java.lang.AssertionError: Expected exception: java.lang.NumberFormatException
 
           .put("lang.Integer.getIntegerLjava_lang_StringI.Integer_getInteger_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, DexVm.Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: expected:<6031769> but was:<1>
 
           .put("lang.Integer.valueOfLjava_lang_String.Integer_valueOf_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t07
           // java.lang.AssertionError: NumberFormatException expected for input: +1
 
           .put("lang.Integer.decodeLjava_lang_String.Integer_decode_A04",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t06
           // java.lang.AssertionError: Expected exception: java.lang.NumberFormatException
 
           .put("lang.Integer.parseIntLjava_lang_StringI.Integer_parseInt_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t06
           // java.lang.AssertionError: Expected exception: java.lang.NumberFormatException
 
           .put("lang.Integer.getIntegerLjava_lang_StringLjava_lang_Integer.Integer_getInteger_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: expected:<6031769> but was:<1>
 
           .put("lang.Integer.ConstructorLjava_lang_String.Integer_Constructor_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t06
           // java.lang.AssertionError: Expected exception: java.lang.NumberFormatException
 
           .put("lang.Integer.getIntegerLjava_lang_String.Integer_getInteger_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: expected null, but was:<1>
 
           .put("lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t04
           // java.lang.AssertionError: reference is not enqueued after 2 sec
 
           .put("lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: reference is not enqueued after 2 sec
 
@@ -1903,7 +1906,7 @@
           // java.lang.AssertionError: expected null, but was:<[I@e2603b4>
 
           .put("lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: reference is not enqueued after 2 sec
 
@@ -1916,12 +1919,12 @@
           // java.lang.AssertionError: expected null, but was:<[I@1b32f32>
 
           .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: reference is not enqueued after 2 sec
 
           .put("lang.StackTraceElement.toString.StackTraceElement_toString_A01",
-              match(runtimes(DexVm.ART_DEFAULT)))
+              match(runtimes(Version.DEFAULT)))
           // 1) t03
           // org.junit.ComparisonFailure: expected:<...ethod(Unknown Source[])> but was:<...ethod(Unknown Source[:1])>
 
@@ -1989,7 +1992,7 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/ClassFormatError/serialization/ClassFormatError_serialization_A01.golden.0.ser
 
           .put("lang.Math.cbrtD.Math_cbrt_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1)))
           // 1) t01
           // java.lang.AssertionError: cbrt(27.) expected:<3.0> but was:<3.0000000000000004>
 
@@ -2022,12 +2025,12 @@
           // org.junit.ComparisonFailure: expected:<0.001[0]> but was:<0.001[]>
 
           .put("lang.Short.valueOfLjava_lang_StringI.Short_valueOf_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: Missing NumberFormatException for radix=10
 
           .put("lang.Short.valueOfLjava_lang_String.Short_valueOf_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t03
           // java.lang.AssertionError: Missing NumberFormatException for arg="+1"
 
@@ -2036,17 +2039,17 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/Short/serialization/Short_serialization_A01.golden.0.ser
 
           .put("lang.Short.parseShortLjava_lang_String.Short_parseShort_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Parsed Short instance from string:+1
 
           .put("lang.Short.decodeLjava_lang_String.Short_decode_A04",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Decoded Short instance from string:+1
 
           .put("lang.Short.ConstructorLjava_lang_String.Short_Constructor_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t02
           // java.lang.AssertionError: Created Short instance from string:+1
 
@@ -2066,13 +2069,13 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/annotation/AnnotationFormatError/serialization/AnnotationFormatError_serialization_A01.golden.0.ser
 
           .put("lang.Short.parseShortLjava_lang_StringI.Short_parseShort_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Parsed Short instance from string:+1 radix:10
 
           .put(
               "lang.annotation.IncompleteAnnotationException.ConstructorLjava_lang_ClassLjava_lang_String.IncompleteAnnotationException_Constructor_A01",
-              match(runtimes(DexVm.ART_DEFAULT)))
+              match(runtimes(Version.DEFAULT)))
           // 1) t02
           // java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.String.toString()' on a null object reference
           // 2) t04
@@ -2286,7 +2289,7 @@
           // java.lang.AssertionError: expected null, but was:<class com.google.jctf.test.lib.java.lang.Class.getDeclaringClass.Class_getDeclaringClass_A01>
 
           .put("lang.Class.getDeclaredFields.Class_getDeclaredFields_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t02
           // java.lang.AssertionError: array lengths differed, expected.length=0 actual.length=2
 
@@ -2397,7 +2400,7 @@
           // java.lang.AssertionError: Vague error message
 
           .put("lang.Class.getClasses.Class_getClasses_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_4_4_4)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V4_4_4)))
           // 1) t03
           // java.lang.AssertionError: Array lengths expected:<2> but was:<3>
 
@@ -2412,12 +2415,12 @@
           // java.lang.SecurityException
 
           .put("lang.Class.getDeclaredMethods.Class_getDeclaredMethods_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) t03
           // java.lang.AssertionError: Array lengths expected:<1> but was:<3>
 
           .put("lang.Class.getMethods.Class_getMethods_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) t03
           // java.lang.AssertionError: Array lengths expected:<1> but was:<3>
 
@@ -2573,7 +2576,7 @@
           // Caused by: java.lang.AssertionError: Unable to configure default providers
 
           .put("lang.Class.getMethodLjava_lang_String_Ljava_lang_Class.Class_getMethod_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_4_4_4)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V4_4_4)))
           // 1) t04
           // java.lang.AssertionError: expected:<interface com.google.jctf.test.lib.java.lang.Class.getMethodLjava_lang_String_Ljava_lang_Class.Class_getMethod_A01$I1> but was:<interface com.google.jctf.test.lib.java.lang.Class.getMethodLjava_lang_String$Ljava_lang_Class.Class_getMethod_A01$I2>
 
@@ -2701,7 +2704,7 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/lang/String/CASE_INSENSITIVE_ORDER/serialization/String_serialization_A01.golden.0.ser
 
           .put("lang.String.getBytesLjava_lang_String.String_getBytes_A14",
-              match(runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V7_0_0, Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t07
           // arrays first differed at element [0]; expected:<-2> but was:<-1>
           // Caused by: java.lang.AssertionError: expected:<-2> but was:<-1>
@@ -2762,7 +2765,7 @@
           // org.junit.ComparisonFailure: Incorrect double string returned expected:<0.001[0]> but was:<0.001[]>
 
           .put("lang.String.getBytesLjava_nio_charset_Charset.String_getBytes_A14",
-              match(runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V7_0_0, Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t07
           // arrays first differed at element [0]; expected:<-2> but was:<-1>
           // Caused by: java.lang.AssertionError: expected:<-2> but was:<-1>
@@ -2805,7 +2808,7 @@
           // Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.net.URL.getProtocol()' on a null object reference
 
           .put("lang.Package.isAnnotationPresentLjava_lang_Class.Package_isAnnotationPresent_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) testIsAnnotationPresent_Null2
           // java.lang.Exception: Unexpected exception, expected<java.lang.NullPointerException> but was<java.lang.ClassNotFoundException>
           // Caused by: java.lang.ClassNotFoundException: com.google.jctf.simpleClass
@@ -2834,7 +2837,7 @@
           // Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.net.URL.getProtocol()' on a null object reference
 
           .put("lang.Package.getAnnotationLjava_lang_Class.Package_getAnnotation_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0)))
           // 1) testGetAnnotation_Null2
           // java.lang.Exception: Unexpected exception, expected<java.lang.NullPointerException> but was<java.lang.ClassNotFoundException>
           // Caused by: java.lang.ClassNotFoundException: com.google.jctf.simpleClass
@@ -3625,7 +3628,7 @@
 
           .put(
               "lang.reflect.Proxy.getInvocationHandlerLjava_lang_Object.Proxy_getInvocationHandler_A02",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t02
           // java.lang.Exception: Unexpected exception, expected<java.lang.IllegalArgumentException> but was<java.lang.NullPointerException>
           // Caused by: java.lang.NullPointerException
@@ -3636,13 +3639,13 @@
           // 2) t05
           // java.lang.NullPointerException: Attempt to invoke virtual method 'int java.io.InputStream.available()' on a null object reference
 
-          .put("lang.reflect.Proxy.Class.Proxy_class_A02", match(runtimes(DexVm.ART_4_4_4)))
+          .put("lang.reflect.Proxy.Class.Proxy_class_A02", match(runtimes(Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError: expected array was null
           // 2) t04
           // java.lang.AssertionError: expected array was null
 
-          .put("lang.reflect.Proxy.Class.Proxy_class_A03", match(runtimes(DexVm.ART_4_4_4)))
+          .put("lang.reflect.Proxy.Class.Proxy_class_A03", match(runtimes(Version.V4_4_4)))
           // 1) t03
           // java.lang.AbstractMethodError: abstract method not implemented
 
@@ -3662,7 +3665,7 @@
           // 2) t05
           // java.lang.NullPointerException: Attempt to invoke virtual method 'int java.io.InputStream.available()' on a null object reference
 
-          .put("lang.reflect.Proxy.h.Proxy_h_A01", match(runtimes(DexVm.ART_DEFAULT)))
+          .put("lang.reflect.Proxy.h.Proxy_h_A01", match(runtimes(Version.DEFAULT)))
           // 1) t01
           // java.lang.reflect.InvocationTargetException
           // Caused by: java.lang.NullPointerException
@@ -3693,7 +3696,7 @@
 
           .put(
               "lang.reflect.Proxy.ConstructorLjava_lang_reflect_InvocationHandler.Proxy_Constructor_A01",
-              match(runtimes(DexVm.ART_DEFAULT)))
+              match(runtimes(Version.DEFAULT)))
           // 1) t01
           // java.lang.NullPointerException
 
@@ -3811,7 +3814,7 @@
 
           .put(
               "lang.reflect.InvocationHandler.invokeLjava_lang_ObjectLjava_lang_reflect_Method_Ljava_lang_Object.InvocationHandler_invoke_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t04
           // java.lang.AssertionError: expected array was null
 
@@ -3826,7 +3829,7 @@
           // java.lang.AssertionError: Expected exception: java.lang.TypeNotPresentException
 
           .put("lang.reflect.Method.hashCode.Method_hashCode_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1)
           // java.lang.AssertionError: expected:<-1918826964> but was:<-1295482945>
 
@@ -3885,7 +3888,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: bytePublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@b1b0f3d
 
           .put("lang.reflect.Field.setByteLjava_lang_ObjectB.Field_setByte_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: bytePublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -3894,7 +3897,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: booleanPublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@953cc4f
 
           .put("lang.reflect.Field.setBooleanLjava_lang_ObjectZ.Field_setBoolean_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: booleanPublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -3926,7 +3929,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: charPublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@95271f1
 
           .put("lang.reflect.Field.setCharLjava_lang_ObjectC.Field_setChar_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: charPublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -3942,7 +3945,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: floatPublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@3fca927
 
           .put("lang.reflect.Field.setFloatLjava_lang_ObjectF.Field_setFloat_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: floatPublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -3953,7 +3956,7 @@
           // java.lang.AssertionError: Misconfiguration: MissingAntn should not be accessible
 
           .put("lang.reflect.Field.setIntLjava_lang_ObjectI.Field_setInt_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: intPublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -4022,7 +4025,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: intPublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@94fbd35
 
           .put("lang.reflect.Field.setDoubleLjava_lang_ObjectD.Field_setDouble_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: doublePublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -4038,7 +4041,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: shortPublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@7887a47
 
           .put("lang.reflect.Field.setLongLjava_lang_ObjectJ.Field_setLong_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: longPublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -4058,7 +4061,7 @@
           // java.lang.AssertionError: Exception is not thrown: field: doublePublicField, object: com.google.jctf.test.lib.java.lang.reflect.Field.TestStaticFinalPrimitiveField@4dd95e2
 
           .put("lang.reflect.Field.setShortLjava_lang_ObjectS.Field_setShort_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: shortPublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -4084,7 +4087,7 @@
           // java.lang.NullPointerException: Attempt to invoke virtual method 'int java.io.InputStream.available()' on a null object reference
 
           .put("lang.reflect.Field.setLjava_lang_ObjectLjava_lang_Object.Field_set_A02",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Illegal exception is thrown: java.lang.IllegalAccessException: field is marked 'final', field: bytePublicField, class: class com.google.jctf.test.lib.java.lang.reflect.Field.TestFinalPrimitiveField
 
@@ -4456,7 +4459,7 @@
 
           .put(
               "util.concurrent.ConcurrentHashMap.serialization.ConcurrentHashMap_serialization_A01",
-              match(runtimes(DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Unable to configure default providers
 
@@ -4515,7 +4518,7 @@
           // java.lang.AssertionError: Destroyed thread group was not finalized
 
           .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01",
-              match(D8_COMPILER, runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(D8_COMPILER, runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.IllegalThreadStateException: Thread group still contains threads: Test group
           // 2) t04
@@ -4524,62 +4527,62 @@
           // java.lang.AssertionError: Destroyed thread group was not finalized
 
           .put("lang.Thread.start.Thread_start_A01",
-              match(runtimes(DexVm.ART_7_0_0)))
+              match(runtimes(Version.V7_0_0)))
           // 1) t01(com.google.jctf.test.lib.java.lang.Thread.start.Thread_start_A01)
           // java.lang.AssertionError: no IllegalThreadStateException 1
 
           .put("lang.String.getBytesLjava_lang_String.String_getBytes_A02",
-              match(runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V7_0_0, Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01(com.google.jctf.test.lib.java.lang.String.getBytesLjava_lang_String.String_getBytes_A02)
           // java.lang.Exception: Unexpected exception, expected<java.lang.NullPointerException> but was<java.io.UnsupportedEncodingException>
 
           .put(
               "util.concurrent.CopyOnWriteArrayList.lastIndexOfLjava_lang_ObjectI.CopyOnWriteArrayList_lastIndexOf_A02",
-              match(runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V7_0_0, Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // java.lang.AssertionError: Expected exception: java.lang.IndexOutOfBoundsException
 
           .put(
               "util.concurrent.CopyOnWriteArrayList.lastIndexOfLjava_lang_ObjectI.CopyOnWriteArrayList_lastIndexOf_A01",
-              match(runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V7_0_0, Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01(com.google.jctf.test.lib.java.util.concurrent.CopyOnWriteArrayList.lastIndexOfLjava_lang_ObjectI.CopyOnWriteArrayList_lastIndexOf_A01)
           // java.lang.AssertionError: expected:<3> but was:<1>
           // 2) t02(com.google.jctf.test.lib.java.util.concurrent.CopyOnWriteArrayList.lastIndexOfLjava_lang_ObjectI.CopyOnWriteArrayList_lastIndexOf_A01)
           // java.lang.ArrayIndexOutOfBoundsException: length=3; index=2147483647
 
           .put("lang.StringBuffer.getCharsII_CI.StringBuffer_getChars_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t03
           // java.lang.NullPointerException: dst == null
 
           .put("lang.StringBuffer.appendF.StringBuffer_append_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError: Buffer is invalid length after append expected:<26> but was:<25>
 
           .put("lang.StringBuffer.insertI_CII.StringBuffer_insert_A02",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.NullPointerException: Attempt to get length of null array
 
           .put("lang.StrictMath.scalbDI.StrictMath_scalb_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Wrong result provided for argument: -1.7976931348623157E308 scaleFactor: 2147483647 expected:<-Infinity> but was:<-0.0>
 
           .put("lang.StrictMath.scalbDI.StrictMath_scalb_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t03
           // java.lang.AssertionError: Wrong result provided for argument: -2.2250738585072014E-308 scaleFactor: -2147483647 expected:<-0.0> but was:<-Infinity>
           // 2) t04
           // java.lang.AssertionError: Wrong result provided for argument: 1.7976931348623157E308 scaleFactor: -2046 expected:<2.2250738585072014E-308> but was:<2.225073858507201E-308>
 
           .put("lang.StrictMath.scalbFI.StrictMath_scalb_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Wrong result provided for argument: -3.4028235E38 scaleFactor: 2147483647 expected:<-Infinity> but was:<-0.0>
 
           .put("lang.StrictMath.scalbFI.StrictMath_scalb_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError: Wrong result provided for argument: 3.4028235E38 scaleFactor: -254 expected:<1.1754943508222875E-38> but was:<1.1754942106924411E-38>
           // 2) t03
@@ -4587,96 +4590,96 @@
 
           .put(
               "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_StringJ.Thread_Constructor_A07",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError: wrong daemonism expected:<true> but was:<false>
 
           .put(
               "lang.Thread.ConstructorLjava_lang_ThreadGroupLjava_lang_RunnableLjava_lang_String.Thread_Constructor_A07",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError: wrong daemonism expected:<true> but was:<false>
 
           .put("lang.Thread.toString.Thread_toString_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError
 
           .put("lang.Thread.start.Thread_start_A02",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.IllegalThreadStateException: Thread group still contains threads: start
 
           .put("lang.Thread.setPriorityI.Thread_setPriority_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError: expected:<5> but was:<10>
 
           .put("lang.ClassLoader.ConstructorLjava_lang_ClassLoader.ClassLoader_Constructor_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.NullPointerException: parentLoader == null && !nullAllowed
           // 2) t03
           // java.lang.NullPointerException: parentLoader == null && !nullAllowed
 
           .put("lang.Enum.compareToLjava_lang_Enum.Enum_compareTo_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Expected exception: java.lang.ClassCastException
 
           .put("lang.Enum.hashCode.Enum_hashCode_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError
 
           .put("lang.StackTraceElement.hashCode.StackTraceElement_hashCode_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // java.lang.AssertionError
 
           .put("lang.ProcessBuilder.environment.ProcessBuilder_environment_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: should throw ClassCastException.
 
           .put("lang.ProcessBuilder.environment.ProcessBuilder_environment_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: should throw ClassCastException.
 
           .put("lang.Float.toStringF.Float_toString_A04",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // org.junit.ComparisonFailure: Invalid string produced for bits: 4efffffa expected:<2.147482[88]E9> but was:<2.147482[9]E9>
 
           .put("lang.Float.toStringF.Float_toString_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t02
           // org.junit.ComparisonFailure: expected:<-1.175494[35]E-38> but was:<-1.175494[4]E-38>
 
           .put("lang.ThreadGroup.getMaxPriority.ThreadGroup_getMaxPriority_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: New value should be the same as we set expected:<2> but was:<1>
 
           .put(
               "lang.ThreadGroup.uncaughtExceptionLjava_lang_ThreadLjava_lang_Throwable.ThreadGroup_uncaughtException_A02",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t05
           // java.lang.AssertionError: Non-informative exception info: java.lang.RuntimeException
 
           .put("lang.ThreadGroup.list.ThreadGroup_list_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t04
           // java.lang.IllegalThreadStateException: Thread group still contains threads: Test group(list)
 
           .put("lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.IllegalThreadStateException: Thread group still contains threads: Test root(setMaxPriority)
 
           .put("lang.ThreadGroup.setMaxPriorityI.ThreadGroup_setMaxPriority_A04",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: New value should be the same as we set expected:<2> but was:<1>
           // 2) t02
@@ -4685,46 +4688,46 @@
           // java.lang.AssertionError: expected:<7> but was:<1>
 
           .put("lang.ThreadGroup.toString.ThreadGroup_toString_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // org.junit.ComparisonFailure: toString does not follow the RI expected:<... group(toString),max[pri]=10]> but was:<... group(toString),max[Priority]=10]>
 
           .put("lang.Class.getFieldLjava_lang_String.Class_getField_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t04
           // java.lang.AssertionError: expected:<interface com.google.jctf.test.lib.java.lang.Class.getFieldLjava_lang_String.Class_getField_A01$I1> but was:<class com.google.jctf.test.lib.java.lang.Class.getFieldLjava_lang_String.Class_getField_A01$S1>
 
           .put("lang.String.replaceCC.String_replace_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t04
           // java.lang.AssertionError: expected same:<aaaaaa> was not:<aaaaaa>
 
           .put("lang.Package.isCompatibleWithLjava_lang_String.Package_isCompatibleWith_A02",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: NumberFormatException isn't thrown for desired . and current 1.0
           // 2) t03
           // java.lang.AssertionError: NumberFormatException isn't thrown for desired 1.0 and current .
 
           .put("lang.StringBuilder.appendF.StringBuilder_append_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Invalid length of created builder expected:<14> but was:<13>
 
           .put("lang.StringBuilder.insertIF.StringBuilder_insert_A01",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Invalid length of created builder expected:<14> but was:<13>
 
           .put(
               "lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A04",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: SecurityException expected.
 
           .put(
               "lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A04",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: SecurityException expected.
           // 2) t02
@@ -4735,23 +4738,23 @@
           // java.lang.AssertionError: SecurityException expected.
 
           .put("lang.Character.UnicodeBlock.forName_java_lang_String.UnicodeBlock_forName_A03",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t01
           // java.lang.AssertionError: Expected exception: java.lang.IllegalArgumentException
 
           .put("lang.System.loadLjava_lang_String.System_load_A02",
-              match(runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+              match(runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           // 1) t03
           // java.lang.AssertionError: Expected exception: java.lang.UnsatisfiedLinkError
 
           .put("lang.StrictMath.nextAfterFD.StrictMath_nextAfter_A01",
-              match(R8_NOT_AFTER_D8_COMPILER, runtimes(DexVm.ART_5_1_1)))
+              match(R8_NOT_AFTER_D8_COMPILER, runtimes(Version.V5_1_1)))
           // 1) t01
           // java.lang.AssertionError: Wrong value returned for start: Infinity direction: NaN expected:<Infinity> but was:<NaN>
           // 2) t02
           // java.lang.AssertionError: Wrong value returned for start: -0.0 direction: NaN expected:<-1.4E-45> but was:<NaN>
 
-          .put("lang.Math.hypotDD.Math_hypot_A04", match(runtimes(DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+          .put("lang.Math.hypotDD.Math_hypot_A04", match(runtimes(Version.V5_1_1, Version.V4_4_4)))
           // 1) t04
           // java.lang.AssertionError
 
@@ -4796,23 +4799,23 @@
           // java.lang.AssertionError: java.lang.AssertionError: expected:<7> but was:<6>
 
           .put("lang.ref.PhantomReference.clear.PhantomReference_clear_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           .put("lang.ref.SoftReference.clear.SoftReference_clear_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           .put("lang.ref.WeakReference.clear.WeakReference_clear_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // Passes or fails randomly. Have seen out of memory and assertion errors.
 
           .put("lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           .put("lang.ref.SoftReference.isEnqueued.SoftReference_isEnqueued_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // Passes or fails randomly. Check that something is enqueued after 2 seconds.
 
           .put("lang.ref.ReferenceQueue.poll.ReferenceQueue_poll_A01",
-              match(runtimes(DexVm.ART_4_4_4)))
+              match(runtimes(Version.V4_4_4)))
           // Passes or fails randomly.
 
           .build(); // end of flakyWithArt
@@ -4942,7 +4945,7 @@
       CompilationMode compilationMode) {
     Collection<TestCondition> entries = testConditions.get(name);
     for (TestCondition entry : entries) {
-      if (entry.test(DexTool.NONE, compilerUnderTest, dexVm, compilationMode)) {
+      if (entry.test(DexTool.NONE, compilerUnderTest, dexVm.getVersion(), compilationMode)) {
         return true;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index c379193..5fe7c16 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.TestCondition.RuntimeSet;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
@@ -83,10 +85,10 @@
   private static final String ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR = "tests/2016-12-19/art/lib64";
 
   private static final RuntimeSet LEGACY_RUNTIME = TestCondition.runtimes(
-      DexVm.ART_4_4_4,
-      DexVm.ART_5_1_1,
-      DexVm.ART_6_0_1,
-      DexVm.ART_7_0_0);
+      DexVm.Version.V4_4_4,
+      DexVm.Version.V5_1_1,
+      DexVm.Version.V6_0_1,
+      DexVm.Version.V7_0_0);
 
   // Input jar for jctf tests.
   private static final String JCTF_COMMON_JAR = "build/libs/jctfCommon.jar";
@@ -146,14 +148,16 @@
   private static final Multimap<String, TestCondition> timeoutOrSkipRunWithArt =
       new ImmutableListMultimap.Builder<String, TestCondition>()
           // Loops on art - timeout.
-          .put("109-suspend-check", TestCondition.match(TestCondition.runtimes(DexVm.ART_5_1_1)))
+          .put("109-suspend-check",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Flaky loops on art.
-          .put("129-ThreadGetId", TestCondition.match(TestCondition.runtimes(DexVm.ART_5_1_1)))
+          .put("129-ThreadGetId", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Takes ages to run on art 5.1.1 and behaves the same as on 6.0.1. Running this
           // tests on 5.1.1 makes our buildbot cycles time too long.
-          .put("800-smali", TestCondition.match(TestCondition.runtimes(DexVm.ART_5_1_1)))
+          .put("800-smali", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Hangs on dalvik.
-          .put("802-deoptimization", TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+          .put("802-deoptimization",
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           .build();
 
   // Tests that are flaky with the Art version we currently use.
@@ -183,10 +187,10 @@
           // Failed on buildbot with: terminate called after throwing an instance
           // of '__gnu_cxx::recursive_init_error'
           .put("096-array-copy-concurrent-gc",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Sometimes fails with out of memory on Dalvik.
           .put("114-ParallelGC",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Seen crash: currently no more information
           .put("144-static-field-sigquit", TestCondition.any())
           // Opens a lot of file descriptors and depending on the state of the machine this
@@ -417,8 +421,8 @@
       "617-clinit-oome"
   );
 
-  private static Map<DexVm, List<String>> expectedToFailRunWithArtVersion = ImmutableMap.of(
-      DexVm.ART_7_0_0, ImmutableList.of(
+  private static Map<DexVm.Version, List<String>> expectedToFailRunWithArtVersion = ImmutableMap.of(
+      DexVm.Version.V7_0_0, ImmutableList.of(
           // Generally fails on non-R8/D8 running.
           "412-new-array",
           "610-arraycopy",
@@ -426,7 +430,7 @@
           // Crashes the VM, cause is unclear.
           "080-oom-throw"
       ),
-      DexVm.ART_6_0_1, ImmutableList.of(
+      DexVm.Version.V6_0_1, ImmutableList.of(
           // Generally fails on non-R8/D8 running.
           "004-checker-UnsafeTest18",
           "005-annotations",
@@ -445,7 +449,7 @@
           // Crashes the VM, cause is unclear.
           "080-oom-throw"
       ),
-      DexVm.ART_5_1_1, ImmutableList.of(
+      DexVm.Version.V5_1_1, ImmutableList.of(
           // Generally fails on non R8/D8 running.
           "004-checker-UnsafeTest18",
           "004-NativeAllocations",
@@ -463,7 +467,7 @@
           "605-new-string-from-bytes",
           "626-const-class-linking"
       ),
-      DexVm.ART_4_4_4, ImmutableList.of(
+      DexVm.Version.V4_4_4, ImmutableList.of(
           // Generally fails on non R8/D8 running.
           "004-checker-UnsafeTest18",
           "004-NativeAllocations",
@@ -494,12 +498,13 @@
           .put("064-field-access",
               TestCondition.match(
                   TestCondition.R8_NOT_AFTER_D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
           .put("064-field-access",
               TestCondition.match(
                   TestCondition.R8_COMPILER,
                   TestCondition.runtimes(
-                      DexVm.ART_DEFAULT, DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+                      DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
+                      DexVm.Version.V5_1_1)))
           // The growth limit test fails after processing by R8 because R8 will eliminate an
           // "unneeded" const store. The following reflective call to the VM's GC will then see the
           // large array as still live and the subsequent allocations will fail to reach the desired
@@ -512,71 +517,73 @@
               "461-get-reference-vreg",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+                  TestCondition
+                      .runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
           // Dalvik fails on reading an uninitialized local.
           .put(
               "471-uninitialized-locals",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Out of memory.
           .put("152-dead-large-object",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Cannot resolve exception handler. Interestingly, D8 generates different code in
           // release mode (which is also the code generated by R8) which passes.
           .put("111-unresolvable-exception",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Type not present.
           .put("124-missing-classes",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Failed creating vtable.
           .put("587-inline-class-error",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Failed creating vtable.
           .put("595-error-class",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // NoSuchFieldException: systemThreadGroup on Art 4.4.4.
           .put("129-ThreadGetId",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Verifier says: can't modify final field LMain;.staticFinalField.
           .put("600-verifier-fails",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // VFY: args to if-eq/if-ne must both be refs or cat1.
           .put("134-reg-promotion",
               TestCondition.match(
                   TestCondition.R8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
           .put("134-reg-promotion",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.JACK),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // VFY: tried to get class from non-ref register.
           .put("506-verify-aput",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // NoSuchMethod: startMethodTracing.
           .put("545-tracing-and-jit",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // filled-new-array arg 0(1) not valid.
           .put("412-new-array",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // TODO(ager): unclear what is failing here.
           .put("098-ddmc",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Get unexpected modifier bits on dalvik.
           .put("121-modifiers", TestCondition.match(
               TestCondition.tools(DexTool.DX),
-              TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Unsatisfiable link error:
           // libarttest.so: undefined symbol: _ZN3art6Thread18RunEmptyCheckpointEv
           .put(
               "543-env-long-ref",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+                  TestCondition
+                      .runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1, DexVm.Version.V5_1_1)))
           // lib64 libarttest.so: wrong ELF class ELFCLASS64.
           .put("543-env-long-ref",
-              TestCondition.match(TestCondition.runtimes(DexVm.ART_4_4_4)))
+              TestCondition.match(TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // Regression test for an issue that is not fixed on version 5.1.1. Throws an Exception
           // instance instead of the expected NullPointerException. This bug is only tickled when
           // running the R8 generated code when starting from jar or from dex code generated with
@@ -587,14 +594,15 @@
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.DX),
                   TestCondition.R8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_5_1_1)))
+                  TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
           // iput on a static field.
           .put(
               "600-verifier-fails",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_7_0_0, DexVm.ART_6_0_1, DexVm.ART_5_1_1)))
+                  TestCondition.runtimes(DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
+                      DexVm.Version.V5_1_1)))
           .build();
 
   // Tests where the output of R8/D8 runs in Art but produces different output than the expected.txt
@@ -605,12 +613,12 @@
           .put("072-precise-gc",
               TestCondition.match(
                   TestCondition.R8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
           .put("072-precise-gc",
               TestCondition.match(
                   TestCondition.tools(DexTool.JACK, DexTool.NONE),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4)))
           // This one is expected to have different output. It counts instances, but the list that
           // keeps the instances alive is dead and could be garbage collected. The compiler reuses
           // the register for the list and therefore there are no live instances.
@@ -621,23 +629,25 @@
               "800-smali",
               TestCondition.match(
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_5_1_1, DexVm.ART_6_0_1)))
+                  TestCondition.runtimes(DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
           // Triggers regression test in 6.0.1 when using R8/D8 in debug mode.
           .put(
               "474-fp-sub-neg",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.JACK),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_6_0_1)))
+                  TestCondition.runtimes(DexVm.Version.V6_0_1)))
           .build();
 
   private static final TestCondition beforeAndroidN =
       TestCondition
-          .match(TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1));
+          .match(TestCondition
+              .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1));
   private static final TestCondition beforeAndroidO =
       TestCondition.match(
           TestCondition.runtimes(
-              DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1, DexVm.ART_7_0_0));
+              DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1,
+              DexVm.Version.V7_0_0));
 
   // TODO(ager): Could we test that these fail in the way that we expect?
   private static final Multimap<String, TestCondition> expectedToFailRunWithArt =
@@ -666,7 +676,8 @@
               TestCondition.match(
                   TestCondition.tools(DexTool.JACK, DexTool.DX),
                   TestCondition.compilers(CompilerUnderTest.R8, CompilerUnderTest.D8),
-                  TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1)))
+                  TestCondition
+                      .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
           // Array index out of bounds exception.
           .put("449-checker-bce", TestCondition.any())
           // Fails: get_vreg_jni.cc:46] Check failed: value == 42u (value=314630384, 42u=42)
@@ -675,28 +686,28 @@
           .put(
               "454-get-vreg",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1,
-                      DexVm.ART_6_0_1, DexVm.ART_7_0_0)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
+                      DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
           .put(
               "454-get-vreg",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_DEFAULT)))
+                  TestCondition.runtimes(DexVm.Version.DEFAULT)))
           .put("454-get-vreg", TestCondition.match(TestCondition.R8_COMPILER))
           // Fails: regs_jni.cc:42] Check failed: GetVReg(m, 0, kIntVReg, &value)
           // The R8/D8 code does not put values in the same registers as the tests expects.
           .put(
               "457-regs",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1,
-                      DexVm.ART_6_0_1, DexVm.ART_7_0_0)))
+                  TestCondition.runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1,
+                      DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
           .put(
               "457-regs",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE),
                   TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.ART_DEFAULT)))
+                  TestCondition.runtimes(DexVm.Version.DEFAULT)))
           .put("457-regs", TestCondition.match(TestCondition.R8_COMPILER))
           // Class not found.
           .put("529-checker-unresolved", TestCondition.any())
@@ -753,11 +764,13 @@
           // Uses dex file version 37 and therefore only runs on Android N and above.
           .put("972-iface-super-multidex",
               TestCondition.match(TestCondition.tools(DexTool.JACK, DexTool.DX),
-                  TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1)))
+                  TestCondition
+                      .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
           // Uses dex file version 37 and therefore only runs on Android N and above.
           .put("978-virtual-interface",
               TestCondition.match(TestCondition.tools(DexTool.JACK, DexTool.DX),
-                  TestCondition.runtimes(DexVm.ART_4_4_4, DexVm.ART_5_1_1, DexVm.ART_6_0_1)))
+                  TestCondition
+                      .runtimes(DexVm.Version.V4_4_4, DexVm.Version.V5_1_1, DexVm.Version.V6_0_1)))
           .build();
 
   // Tests where code generation fails.
@@ -1015,12 +1028,12 @@
   private static Set<String> collectTestsMatchingConditions(
       DexTool dexTool,
       CompilerUnderTest compilerUnderTest,
-      DexVm dexVm,
+      DexVm.Version dexVmVersion,
       CompilationMode mode,
       Multimap<String, TestCondition> testConditionsMap) {
     Set<String> set = Sets.newHashSet();
     for (Map.Entry<String, TestCondition> kv : testConditionsMap.entries()) {
-      if (kv.getValue().test(dexTool, compilerUnderTest, dexVm, mode)) {
+      if (kv.getValue().test(dexTool, compilerUnderTest, dexVmVersion, mode)) {
         set.add(kv.getKey());
       }
     }
@@ -1028,9 +1041,9 @@
   }
 
   private static Map<SpecificationKey, TestSpecification> getTestsMap(
-      CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) {
+      CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm.Version version) {
     File artTestDir = new File(ART_TESTS_DIR);
-    if (LEGACY_RUNTIME.set.contains(dexVm)) {
+    if (LEGACY_RUNTIME.set.contains(version)) {
       artTestDir = new File(ART_LEGACY_TESTS_DIR);
     }
     if (!artTestDir.exists()) {
@@ -1053,11 +1066,11 @@
       // Collect the tests failing code generation.
       Set<String> failsWithCompiler =
           collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, failingWithCompiler);
+              dexTool, compilerUnderTest, version, compilationMode, failingWithCompiler);
 
       // Collect the tests that are flaky.
       skipArt.addAll(collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, flakyRunWithArt));
+              dexTool, compilerUnderTest, version, compilationMode, flakyRunWithArt));
 
       // Collect tests that has no input:
       if (dexTool == DexTool.NONE) {
@@ -1066,43 +1079,44 @@
 
       // Collect the test that we should skip in this configuration
       skipTest.addAll(collectTestsMatchingConditions(
-          dexTool, compilerUnderTest, dexVm, compilationMode, testToSkip));
+          dexTool, compilerUnderTest, version, compilationMode, testToSkip));
 
       // Collect the test that we should skip in this configuration.
       skipArt.addAll(
           collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, timeoutOrSkipRunWithArt));
+              dexTool, compilerUnderTest, version, compilationMode, timeoutOrSkipRunWithArt));
 
       // Collect the tests failing to run in Art (we still run R8/D8 on these).
       Set<String> failsWithArt =
           collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, failingRunWithArt);
+              dexTool, compilerUnderTest, version, compilationMode, failingRunWithArt);
       {
         Set<String> tmpSet =
             collectTestsMatchingConditions(
-                dexTool, compilerUnderTest, dexVm, compilationMode, expectedToFailRunWithArt);
+                dexTool, compilerUnderTest, version, compilationMode, expectedToFailRunWithArt);
         failsWithArt.addAll(tmpSet);
       }
 
-      if (!ToolHelper.isDefaultDexVm(dexVm)) {
+      if (!ToolHelper.isDefaultDexVm(ToolHelper.getDexVm())) {
         // Generally failing when not TOT art.
         failsWithArt.addAll(expectedToFailRunWithArtNonDefault);
         // Version specific failures
-        failsWithArt.addAll(expectedToFailRunWithArtVersion.get(dexVm));
+        failsWithArt
+            .addAll(expectedToFailRunWithArtVersion.get(ToolHelper.getDexVm().getVersion()));
       }
 
       // Collect the tests failing with output differences in Art.
       Set<String> failsRunWithArtOutput =
           collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, failingRunWithArtOutput);
+              dexTool, compilerUnderTest, version, compilationMode, failingRunWithArtOutput);
       Set<String> expectedToFailWithCompilerSet =
           collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, expectedToFailWithCompiler);
+              dexTool, compilerUnderTest, version, compilationMode, expectedToFailWithCompiler);
 
       // Collect the tests where the original works in Art and the R8/D8 generated output does not.
       Set<String> failsRunWithArtOriginalOnly =
           collectTestsMatchingConditions(
-              dexTool, compilerUnderTest, dexVm, compilationMode, failingRunWithArtOriginalOnly);
+              dexTool, compilerUnderTest, version, compilationMode, failingRunWithArtOriginalOnly);
 
       File compilerTestDir = artTestDir.toPath().resolve(dexToolDirectory(dexTool)).toFile();
       File[] testDirs = compilerTestDir.listFiles();
@@ -1112,7 +1126,7 @@
         // Skip all tests compiled to dex with jack on Dalvik. They have a too high dex
         // version number in the generated output.
         boolean skip = skipTest.contains(name) ||
-            (dexTool == DexTool.JACK && dexVm == DexVm.ART_4_4_4);
+            (dexTool == DexTool.JACK && version == DexVm.Version.V4_4_4);
         // All the native code for all Art tests is currently linked into the
         // libarttest.so file.
         data.put(
@@ -1177,9 +1191,7 @@
       if (artVersion != DexVm.ART_DEFAULT) {
         artTestNativeLibraryDir = new File(ART_LEGACY_TESTS_NATIVE_LIBRARY_DIR);
       }
-      builder.appendArtSystemProperty(
-          "java.library.path",
-          artTestNativeLibraryDir.getAbsolutePath());
+      builder.addToJavaLibraryPath(artTestNativeLibraryDir);
       builder.appendProgramArgument(specification.nativeLibrary);
     }
     return builder;
@@ -1480,7 +1492,7 @@
     }
 
     ArtCommandBuilder builder = buildArtCommand(processedFile, specification, dexVm);
-    if (ToolHelper.getDexVm() != DexVm.ART_4_4_4) {
+    if (ToolHelper.getDexVm() != DexVm.ART_4_4_4_HOST) {
       builder.appendArtOption("-Ximage:/system/non/existent/image.art");
     }
     for (String s : ToolHelper.getBootLibs()) {
@@ -1516,11 +1528,11 @@
     CompilationMode compilationMode = defaultCompilationMode(compilerUnderTest);
 
     TestSpecification specification =
-        getTestsMap(firstCompilerUnderTest, compilationMode, version)
+        getTestsMap(firstCompilerUnderTest, compilationMode, version.getVersion())
             .get(new SpecificationKey(name, toolchain));
 
     if (specification == null) {
-      if (version == DexVm.ART_DEFAULT) {
+      if (version.getVersion() == DexVm.Version.DEFAULT) {
         throw new RuntimeException("Test " + name + " has no specification for toolchain"
             + toolchain + ".");
       } else {
@@ -1534,6 +1546,11 @@
       return;
     }
 
+    if (specification.nativeLibrary != null && ToolHelper.getDexVm().getKind() == Kind.TARGET) {
+      // JNI tests not yet supported for devices
+      return;
+    }
+
     File[] inputFiles;
     if (toolchain == DexTool.NONE) {
       inputFiles = addFileTree(new File[0], new File(specification.directory, "classes"));
@@ -1564,7 +1581,7 @@
     if (compilerUnderTest == CompilerUnderTest.R8_AFTER_D8) {
       compilationMode = CompilationMode.DEBUG;
       specification =
-          getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, version)
+          getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, version.getVersion())
               .get(new SpecificationKey(name, DexTool.DX));
 
       if (specification == null) {
@@ -1673,7 +1690,7 @@
       }
 
       File checkCommand = specification.resolveFile("check");
-      if (checkCommand.exists()) {
+      if (checkCommand.exists() && !ToolHelper.isWindows()) {
         // Run the Art test custom check command.
         File actualFile = temp.newFile();
         com.google.common.io.Files.asByteSink(actualFile).write(output.getBytes(Charsets.UTF_8));
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index b43b041..ebf4749 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
@@ -19,21 +20,21 @@
 
 public class R8RunExamplesAndroidOTest extends RunExamplesAndroidOTest<R8Command.Builder> {
 
-  private static Map<DexVm, List<String>> alsoFailsOn =
+  private static Map<DexVm.Version, List<String>> alsoFailsOn =
       ImmutableMap.of(
-          DexVm.ART_4_4_4, ImmutableList.of(
+          Version.V4_4_4, ImmutableList.of(
               "invokecustom-with-shrinking"
           ),
-          DexVm.ART_5_1_1, ImmutableList.of(
+          Version.V5_1_1, ImmutableList.of(
               "invokecustom-with-shrinking"
           ),
-          DexVm.ART_6_0_1, ImmutableList.of(
+          Version.V6_0_1, ImmutableList.of(
               "invokecustom-with-shrinking"
           ),
-          DexVm.ART_7_0_0, ImmutableList.of(
+          Version.V7_0_0, ImmutableList.of(
               "invokecustom-with-shrinking"
           ),
-          DexVm.ART_DEFAULT, ImmutableList.of(
+          Version.DEFAULT, ImmutableList.of(
           )
       );
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index c8561b8..5c7d303 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.R8RunArtTestsTest.DexTool;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.FileUtils;
@@ -64,7 +65,7 @@
           .put(
               "filledarray.FilledArray",
               TestCondition.match(
-                  TestCondition.runtimes(DexVm.ART_6_0_1, DexVm.ART_5_1_1, DexVm.ART_4_4_4)))
+                  TestCondition.runtimes(Version.V6_0_1, Version.V5_1_1, Version.V4_4_4)))
           .build();
 
   @Parameters(name = "{0}_{1}_{2}_{3}")
@@ -250,7 +251,7 @@
     // this explicit loop to get rid of repeated testing on the buildbots.
     for (DexVm version : artVersions) {
       TestCondition condition = failingRun.get(mainClass);
-      if (condition != null && condition.test(getTool(), compiler, version, mode)) {
+      if (condition != null && condition.test(getTool(), compiler, version.getVersion(), mode)) {
         thrown.expect(Throwable.class);
       } else {
         thrown = ExpectedException.none();
@@ -261,17 +262,17 @@
           ToolHelper.checkArtOutputIdentical(original, generated.toString(), mainClass, version);
 
       // Check output against JVM output.
-      if (shouldMatchJVMOutput(version)) {
+      if (shouldMatchJVMOutput(version.getVersion())) {
         String javaOutput = javaResult.stdout;
         assertEquals(
             "JVM and Art output differ:\n" + "JVM:\n" + javaOutput + "\nArt:\n" + output,
-            output,
-            javaOutput);
+            javaOutput,
+            output);
       }
     }
   }
 
-  private boolean shouldMatchJVMOutput(DexVm version) {
+  private boolean shouldMatchJVMOutput(DexVm.Version version) {
     TestCondition condition = outputNotIdenticalToJVMOutput.get(mainClass);
     return condition == null || !condition.test(getTool(), compiler, version, mode);
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 2de3624..cf0b71e 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
@@ -35,13 +36,13 @@
   private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR;
 
   // Tests where the original smali code fails on Art, but runs after R8 processing.
-  private static Map<DexVm, List<String>> originalFailingOnArtVersions = ImmutableMap.of(
-      DexVm.ART_5_1_1, ImmutableList.of(
+  private static Map<DexVm.Version, List<String>> originalFailingOnArtVersions = ImmutableMap.of(
+      Version.V5_1_1, ImmutableList.of(
           // Smali code contains an empty switch payload.
           "sparse-switch",
           "regression/33846227"
       ),
-      DexVm.ART_4_4_4, ImmutableList.of(
+      Version.V4_4_4, ImmutableList.of(
           // Smali code contains an empty switch payload.
           "sparse-switch",
           "regression/33846227"
@@ -49,19 +50,20 @@
   );
 
   // Tests where the output has a different output than the original on certain VMs.
-  private static Map<DexVm, Map<String, String>> customProcessedOutputExpectation = ImmutableMap.of(
-      DexVm.ART_4_4_4, ImmutableMap.of(
-          "bad-codegen", "java.lang.NullPointerException\n",
-          "type-confusion-regression2", "java.lang.NullPointerException\n",
-          "type-confusion-regression3", "java.lang.NullPointerException\n",
-          "merge-blocks-regression", "java.lang.NullPointerException\n"
-      )
-  );
+  private static Map<DexVm.Version, Map<String, String>> customProcessedOutputExpectation =
+      ImmutableMap.of(
+          Version.V4_4_4, ImmutableMap.of(
+              "bad-codegen", "java.lang.NullPointerException\n",
+              "type-confusion-regression2", "java.lang.NullPointerException\n",
+              "type-confusion-regression3", "java.lang.NullPointerException\n",
+              "merge-blocks-regression", "java.lang.NullPointerException\n"
+          )
+      );
 
   // Tests where the input fails with a verification error on Dalvik instead of the
   // expected runtime exception.
-  private static Map<DexVm, List<String>> dalvikVerificationError = ImmutableMap.of(
-      DexVm.ART_4_4_4, ImmutableList.of(
+  private static Map<DexVm.Version, List<String>> dalvikVerificationError = ImmutableMap.of(
+      Version.V4_4_4, ImmutableList.of(
           // The invokes are in fact invalid, but the test expects the current Art behavior
           // of throwing an IncompatibleClassChange exception. Dalvik fails to verify.
           "illegal-invokes"
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
index 03093df..fd29dc1 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -104,17 +104,17 @@
     abstract void build(Path inputFile, Path out) throws Throwable;
   }
 
-  private static Map<DexVm, List<String>> failsOn =
+  private static Map<DexVm.Version, List<String>> failsOn =
       ImmutableMap.of(
-          DexVm.ART_4_4_4,
+          DexVm.Version.V4_4_4,
           ImmutableList.of(),
-          DexVm.ART_5_1_1,
+          DexVm.Version.V5_1_1,
           ImmutableList.of(),
-          DexVm.ART_6_0_1,
+          DexVm.Version.V6_0_1,
           ImmutableList.of(),
-          DexVm.ART_7_0_0,
+          DexVm.Version.V7_0_0,
           ImmutableList.of(),
-          DexVm.ART_DEFAULT,
+          DexVm.Version.DEFAULT,
           ImmutableList.of());
 
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 84f2768..08f196c 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
 import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
@@ -174,9 +175,9 @@
       ImmutableList.of(
           "invokepolymorphic-error-due-to-min-sdk", "invokecustom-error-due-to-min-sdk");
 
-  private static Map<DexVm, List<String>> failsOn =
+  private static Map<DexVm.Version, List<String>> failsOn =
       ImmutableMap.of(
-          DexVm.ART_4_4_4, ImmutableList.of(
+          DexVm.Version.V4_4_4, ImmutableList.of(
               // API not supported
               "paramnames",
               "repeat_annotations_new_api",
@@ -190,7 +191,7 @@
               "testCallToMissingSuperInterfaceDesugaredAndroidO",
               "testMissingSuperDesugaredAndroidO"
           ),
-          DexVm.ART_5_1_1, ImmutableList.of(
+          DexVm.Version.V5_1_1, ImmutableList.of(
               // API not supported
               "paramnames",
               "repeat_annotations_new_api",
@@ -204,7 +205,7 @@
               "testCallToMissingSuperInterfaceDesugaredAndroidO",
               "testMissingSuperDesugaredAndroidO"
           ),
-          DexVm.ART_6_0_1, ImmutableList.of(
+          DexVm.Version.V6_0_1, ImmutableList.of(
               // API not supported
               "paramnames",
               "repeat_annotations_new_api",
@@ -218,7 +219,7 @@
               "testCallToMissingSuperInterfaceDesugaredAndroidO",
               "testMissingSuperDesugaredAndroidO"
           ),
-          DexVm.ART_7_0_0, ImmutableList.of(
+          DexVm.Version.V7_0_0, ImmutableList.of(
               // API not supported
               "paramnames",
               // Dex version not supported
@@ -229,8 +230,7 @@
               "testCallToMissingSuperInterfaceDesugaredAndroidO",
               "testMissingSuperDesugaredAndroidO"
           ),
-          DexVm.ART_DEFAULT, ImmutableList.of(
-          )
+          DexVm.Version.DEFAULT, ImmutableList.of()
       );
 
   @Rule
@@ -239,9 +239,10 @@
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  boolean failsOn(Map<ToolHelper.DexVm, List<String>> failsOn, String name) {
-    return failsOn.containsKey(ToolHelper.getDexVm())
-        && failsOn.get(ToolHelper.getDexVm()).contains(name);
+  boolean failsOn(Map<ToolHelper.DexVm.Version, List<String>> failsOn, String name) {
+    Version vmVersion = ToolHelper.getDexVm().getVersion();
+    return failsOn.containsKey(vmVersion)
+        && failsOn.get(vmVersion).contains(name);
   }
 
   boolean expectedToFail(String name) {
@@ -395,8 +396,8 @@
           "JVM output does not match art output.\n\tjvm: "
               + javaResult.stdout
               + "\n\tart: "
-              + output.replace("\r", ""),
-          output.equals(javaResult.stdout.replace("\r", "")));
+              + output,
+          output.replace("\r", "").equals(javaResult.stdout.replace("\r", "")));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 438fd37..1aca2bd 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -31,9 +31,9 @@
 
   static class RuntimeSet {
 
-    final EnumSet<DexVm> set;
+    final EnumSet<DexVm.Version> set;
 
-    public RuntimeSet(EnumSet<DexVm> set) {
+    public RuntimeSet(EnumSet<DexVm.Version> set) {
       this.set = set;
     }
   }
@@ -64,19 +64,19 @@
   private static final ToolSet ANY_TOOL = new ToolSet(EnumSet.allOf(DexTool.class));
   private static final CompilerSet ANY_COMPILER =
       new CompilerSet(EnumSet.allOf(CompilerUnderTest.class));
-  private static final RuntimeSet ANY_RUNTIME = new RuntimeSet(EnumSet.allOf(DexVm.class));
+  private static final RuntimeSet ANY_RUNTIME = new RuntimeSet(EnumSet.allOf(DexVm.Version.class));
   private static final CompilationModeSet ANY_MODE =
       new CompilationModeSet(EnumSet.allOf(CompilationMode.class));
 
   private final EnumSet<DexTool> dexTools;
   private final EnumSet<CompilerUnderTest> compilers;
-  private final EnumSet<DexVm> dexVms;
+  private final EnumSet<DexVm.Version> dexVms;
   private final EnumSet<CompilationMode> compilationModes;
 
   public TestCondition(
       EnumSet<DexTool> dexTools,
       EnumSet<CompilerUnderTest> compilers,
-      EnumSet<DexVm> dexVms,
+      EnumSet<DexVm.Version> dexVms,
       EnumSet<CompilationMode> compilationModes) {
     this.dexTools = dexTools;
     this.compilers = compilers;
@@ -94,7 +94,7 @@
     return new CompilerSet(EnumSet.copyOf(Arrays.asList(compilers)));
   }
 
-  public static RuntimeSet runtimes(DexVm... runtimes) {
+  public static RuntimeSet runtimes(DexVm.Version... runtimes) {
     assert runtimes.length > 0;
     return new RuntimeSet(EnumSet.copyOf(Arrays.asList(runtimes)));
   }
@@ -146,11 +146,11 @@
   public boolean test(
       DexTool dexTool,
       CompilerUnderTest compilerUnderTest,
-      DexVm dexVm,
+      DexVm.Version dexVmVersion,
       CompilationMode compilationMode) {
     return dexTools.contains(dexTool)
         && compilers.contains(compilerUnderTest)
-        && dexVms.contains(dexVm)
+        && dexVms.contains(dexVmVersion)
         && compilationModes.contains(compilationMode);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 377d2aa..b97bd7b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -7,6 +7,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -69,11 +71,15 @@
   private static final int DEFAULT_MIN_SDK = AndroidApiLevel.I.getLevel();
 
   public enum DexVm {
-    ART_4_4_4("4.4.4"),
-    ART_5_1_1("5.1.1"),
-    ART_6_0_1("6.0.1"),
-    ART_7_0_0("7.0.0"),
-    ART_DEFAULT("default");
+    ART_4_4_4_TARGET(Version.V4_4_4, Kind.TARGET),
+    ART_4_4_4_HOST(Version.V4_4_4, Kind.HOST),
+    ART_5_1_1_TARGET(Version.V5_1_1, Kind.TARGET),
+    ART_5_1_1_HOST(Version.V5_1_1, Kind.HOST),
+    ART_6_0_1_TARGET(Version.V6_0_1, Kind.TARGET),
+    ART_6_0_1_HOST(Version.V6_0_1, Kind.HOST),
+    ART_7_0_0_TARGET(Version.V7_0_0, Kind.TARGET),
+    ART_7_0_0_HOST(Version.V7_0_0, Kind.HOST),
+    ART_DEFAULT(Version.DEFAULT, Kind.HOST);
 
     private static final ImmutableMap<String, DexVm> SHORT_NAME_MAP =
         new ImmutableMap.Builder<String, DexVm>()
@@ -82,8 +88,45 @@
                     Collectors.toMap(a -> a.toString(), a -> a)))
             .build();
 
+    public enum Version {
+      V4_4_4("4.4.4"),
+      V5_1_1("5.1.1"),
+      V6_0_1("6.0.1"),
+      V7_0_0("7.0.0"),
+      DEFAULT("default");
+
+      Version (String shortName) { this.shortName = shortName; }
+
+      public boolean isNewerThan(Version other) {
+        return compareTo(other) > 0;
+      }
+
+      public boolean isOlderThanOrEqual(Version other) {
+        return compareTo(other) <= 0;
+      }
+
+      public String toString() {
+        return shortName;
+      }
+
+      private String shortName;
+    }
+
+    public enum Kind {
+      HOST("host"),
+      TARGET("target");
+
+      Kind (String shortName) { this.shortName = shortName; }
+
+      public String toString() {
+        return shortName;
+      }
+
+      private String shortName;
+    }
+
     public String toString() {
-      return shortName;
+      return version.shortName + '_' + kind.shortName;
     }
 
     public static DexVm fromShortName(String shortName) {
@@ -91,29 +134,33 @@
     }
 
     public boolean isNewerThan(DexVm other) {
-      return compareTo(other) > 0;
+      return version.isNewerThan(other.version);
     }
 
     public boolean isOlderThanOrEqual(DexVm other) {
-      return compareTo(other) <= 0;
+      return version.isOlderThanOrEqual(other.version);
+    }
+    DexVm(Version version, Kind kind) {
+      this.version = version;
+      this.kind = kind;
     }
 
-    private DexVm(String shortName) {
-      this.shortName = shortName;
-    }
+    public Version getVersion() { return version; }
+    public Kind getKind() { return kind; }
 
-    private final String shortName;
+    private final Version version;
+    private final Kind kind;
   }
 
 
   public abstract static class CommandBuilder {
 
-    private List<String> options = new ArrayList<>();
-    private Map<String, String> systemProperties = new LinkedHashMap<>();
-    private List<String> classpaths = new ArrayList<>();
-    private String mainClass;
-    private List<String> programArguments = new ArrayList<>();
-    private List<String> bootClassPaths = new ArrayList<>();
+    protected List<String> options = new ArrayList<>();
+    protected Map<String, String> systemProperties = new LinkedHashMap<>();
+    protected List<String> classpaths = new ArrayList<>();
+    protected String mainClass;
+    protected List<String> programArguments = new ArrayList<>();
+    protected List<String> bootClassPaths = new ArrayList<>();
 
     public CommandBuilder appendArtOption(String option) {
       options.add(option);
@@ -151,8 +198,10 @@
       // explicitly set it;
       if (shouldUseDocker()) {
         result.add("tools/docker/run.sh");
-      } else {
+      } else if (isLinux())  {
         result.add("/bin/bash");
+      } else {
+        assert isWindows();
       }
       result.add(getExecutable());
       for (String option : options) {
@@ -199,22 +248,49 @@
     private DexVm version;
 
     public ArtCommandBuilder() {
+      this.version = getDexVm();
     }
 
     public ArtCommandBuilder(DexVm version) {
-      assert ART_BINARY_VERSIONS.containsKey(version);
+      if (version.getKind() == Kind.HOST) {
+        assert ART_BINARY_VERSIONS.containsKey(version);
+      }
       this.version = version;
     }
 
     @Override
     protected boolean shouldUseDocker() {
-      return !isLinux();
+      return isMac();
     }
 
     @Override
     protected String getExecutable() {
       return version != null ? getArtBinary(version) : getArtBinary();
     }
+
+    public boolean isForDevice() {
+      return version.getKind() == Kind.TARGET;
+    }
+
+    public ArtCommandBuilder addToJavaLibraryPath(File file) {
+      Assume.assumeTrue("JNI tests are not yet supported on devices", !isForDevice());
+      appendArtSystemProperty("java.library.path", file.getAbsolutePath());
+      return this;
+    }
+
+    public DeviceRunner asDeviceRunner() {
+      return new DeviceRunner()
+              .setVmOptions(options)
+              .setSystemProperties(systemProperties)
+              .setClasspath(toFileList(classpaths))
+              .setBootClasspath(toFileList(bootClassPaths))
+              .setMainClass(mainClass)
+              .setProgramArguments(programArguments);
+    }
+  }
+
+  private static List<File> toFileList(List<String> filePathList) {
+    return filePathList.stream().map(path -> new File(path)).collect(Collectors.toList());
   }
 
   public static class DXCommandBuilder extends CommandBuilder {
@@ -259,29 +335,27 @@
   }
 
   private static final String TOOLS = "tools";
-  private static final String OS_STRING = isLinux() ? "linux" : (isMac() ? "mac" : "windows");
 
   private static final Map<DexVm, String> ART_DIRS =
-      ImmutableMap.of(
-          DexVm.ART_DEFAULT, "art",
-          DexVm.ART_7_0_0, "art-7.0.0",
-          DexVm.ART_6_0_1, "art-6.0.1",
-          DexVm.ART_5_1_1, "art-5.1.1",
-          DexVm.ART_4_4_4, "dalvik");
-
+      ImmutableMap.<DexVm, String>builder()
+          .put(DexVm.ART_DEFAULT, "art")
+              .put(DexVm.ART_7_0_0_HOST, "art-7.0.0")
+              .put(DexVm.ART_6_0_1_HOST, "art-6.0.1")
+              .put(DexVm.ART_5_1_1_HOST, "art-5.1.1")
+              .put(DexVm.ART_4_4_4_HOST, "dalvik").build();
   private static final Map<DexVm, String> ART_BINARY_VERSIONS =
-      ImmutableMap.of(
-          DexVm.ART_DEFAULT, "bin/art",
-          DexVm.ART_7_0_0, "bin/art",
-          DexVm.ART_6_0_1, "bin/art",
-          DexVm.ART_5_1_1, "bin/art",
-          DexVm.ART_4_4_4, "bin/dalvik");
+      ImmutableMap.<DexVm, String>builder()
+              .put(DexVm.ART_DEFAULT, "bin/art")
+              .put(DexVm.ART_7_0_0_HOST, "bin/art")
+              .put(DexVm.ART_6_0_1_HOST, "bin/art")
+              .put(DexVm.ART_5_1_1_HOST, "bin/art")
+              .put(DexVm.ART_4_4_4_HOST, "bin/dalvik").build();
 
   private static final Map<DexVm, String> ART_BINARY_VERSIONS_X64 =
       ImmutableMap.of(
           DexVm.ART_DEFAULT, "bin/art",
-          DexVm.ART_7_0_0, "bin/art",
-          DexVm.ART_6_0_1, "bin/art");
+          DexVm.ART_7_0_0_HOST, "bin/art",
+          DexVm.ART_6_0_1_HOST, "bin/art");
 
   private static final List<String> DALVIK_BOOT_LIBS =
       ImmutableList.of(
@@ -298,13 +372,13 @@
   private static final Map<DexVm, List<String>> BOOT_LIBS =
       ImmutableMap.of(
           DexVm.ART_DEFAULT, ART_BOOT_LIBS,
-          DexVm.ART_7_0_0, ART_BOOT_LIBS,
-          DexVm.ART_6_0_1, ART_BOOT_LIBS,
-          DexVm.ART_5_1_1, ART_BOOT_LIBS,
-          DexVm.ART_4_4_4, DALVIK_BOOT_LIBS);
+          DexVm.ART_7_0_0_HOST, ART_BOOT_LIBS,
+          DexVm.ART_6_0_1_HOST, ART_BOOT_LIBS,
+          DexVm.ART_5_1_1_HOST, ART_BOOT_LIBS,
+          DexVm.ART_4_4_4_HOST, DALVIK_BOOT_LIBS);
 
   private static final String LIB_PATH = TOOLS + "/linux/art/lib";
-  private static final String DX = TOOLS + "/" + OS_STRING + "/dx/bin/dx";
+  private static final String DX = getDxExecutablePath();
   private static final String DEX2OAT = TOOLS + "/linux/art/bin/dex2oat";
   private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
   private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
@@ -322,6 +396,23 @@
     return ""; //never here
   }
 
+  public static String toolsDir() {
+    String osName = System.getProperty("os.name");
+    if (osName.equals("Mac OS X")) {
+      return "mac";
+    } else if (osName.contains("Windows")) {
+      return "windows";
+    } else {
+      return "linux";
+    }
+  }
+
+  private static String getDxExecutablePath() {
+    String toolsDir = toolsDir();
+    String executableName = toolsDir.equals("windows") ? "dx.bat" : "dx";
+    return TOOLS + "/" + toolsDir() + "/dx/bin/" + executableName;
+  }
+
   public static String getArtBinary(DexVm version) {
     String binary = ART_BINARY_VERSIONS.get(version);
     if (binary == null) {
@@ -377,12 +468,15 @@
     String artVersion = System.getProperty("dex_vm");
     if (artVersion != null) {
       DexVm artVersionEnum = getDexVm();
-      if (!ART_BINARY_VERSIONS.containsKey(artVersionEnum)) {
+      if (artVersionEnum.getKind() == Kind.HOST
+          && !ART_BINARY_VERSIONS.containsKey(artVersionEnum)) {
         throw new RuntimeException("Unsupported Art version " + artVersion);
       }
       return ImmutableSet.of(artVersionEnum);
     } else {
-      if (isLinux()) {
+      if (isWindows()) {
+        throw new RuntimeException("You need to specify a runtime with 'dex_vm' property");
+      } else if (isLinux()) {
         return ART_BINARY_VERSIONS.keySet();
       } else {
         return ART_BINARY_VERSIONS_X64.keySet();
@@ -405,6 +499,11 @@
   public static DexVm getDexVm() {
     String artVersion = System.getProperty("dex_vm");
     if (artVersion == null) {
+      if (isWindows()) {
+        throw new RuntimeException(
+            "Default Art version is not supported on Windows. Please specify a non-host runtime "
+                + "with property 'dex_vm'");
+      }
       return DexVm.ART_DEFAULT;
     } else {
       DexVm artVersionEnum = DexVm.fromShortName(artVersion);
@@ -417,10 +516,10 @@
   }
 
   public static int getMinApiLevelForDexVm(DexVm dexVm) {
-    switch (dexVm) {
-      case ART_DEFAULT:
+    switch (dexVm.version) {
+      case DEFAULT:
         return AndroidApiLevel.O.getLevel();
-      case ART_7_0_0:
+      case V7_0_0:
         return AndroidApiLevel.N.getLevel();
       default:
         return AndroidApiLevel.getDefault().getLevel();
@@ -444,7 +543,7 @@
   }
 
   public static boolean artSupported() {
-    if (!isLinux() && !isMac()) {
+    if (!isLinux() && !isMac()  && !isWindows()) {
       System.err.println("Testing on your platform is not fully supported. " +
           "Art does not work on on your platform.");
       return false;
@@ -710,7 +809,16 @@
 
   private static ProcessResult runArtProcess(ArtCommandBuilder builder) throws IOException {
     Assume.assumeTrue(ToolHelper.artSupported());
-    ProcessResult result = runProcess(builder.asProcessBuilder());
+    ProcessResult result;
+    if (builder.isForDevice()) {
+      try {
+        result = builder.asDeviceRunner().run();
+      } catch (DeviceRunnerConfigurationException e) {
+        throw new RuntimeException(e);
+      }
+    } else {
+      result = runProcess(builder.asProcessBuilder());
+    }
     if (result.exitCode != 0) {
       fail("Unexpected art failure: '" + result.stderr + "'\n" + result.stdout);
     }
@@ -768,6 +876,8 @@
 
   public static void runDex2Oat(Path file, Path outFile) throws IOException {
     Assume.assumeTrue(ToolHelper.artSupported());
+    // TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
+    Assume.assumeTrue(!ToolHelper.isWindows());
     assert Files.exists(file);
     assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0;
     List<String> command = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 34c1181..b260663 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -288,6 +290,9 @@
     // Skip test due to unsupported runtime.
     Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
         ToolHelper.artSupported());
+    Assume.assumeTrue("Skipping test " + testName.getMethodName()
+            + " because debug tests are not yet supported on Windows",
+        !ToolHelper.isWindows());
     Assume.assumeFalse(
         "Skipping failing test " + testName.getMethodName() + " for runtime " + ToolHelper
             .getDexVm(), UNSUPPORTED_ART_VERSIONS.contains(ToolHelper.getDexVm()));
@@ -438,7 +443,7 @@
       // when breaking in <clinit>. Last known good version is 7.0.0.
       Assume.assumeTrue(
           "Skipping test " + testName.getMethodName() + " because ART version is not supported",
-          isRunningJava() || ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_7_0_0));
+          isRunningJava() || ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V7_0_0));
       checkStaticField(className, fieldName, fieldSignature, expectedValue);
     });
   }
@@ -610,11 +615,11 @@
           // Set debuggee command-line.
           if (RUNTIME_KIND == RuntimeKind.ART) {
             ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder(ToolHelper.getDexVm());
-            if (ToolHelper.getDexVm().isNewerThan(DexVm.ART_5_1_1)) {
+            if (ToolHelper.getDexVm().getVersion().isNewerThan(DexVm.Version.V5_1_1)) {
               artCommandBuilder.appendArtOption("-Xcompiler-option");
               artCommandBuilder.appendArtOption("--debuggable");
             }
-            if (DEBUG_TESTS && ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4)) {
+            if (DEBUG_TESTS && ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)) {
               artCommandBuilder.appendArtOption("-verbose:jdwp");
             }
             setProperty("jpda.settings.debuggeeJavaPath", artCommandBuilder.build());
diff --git a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
index 03f011f..46ad815 100644
--- a/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/debug/InterfaceMethodTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.android.tools.r8.debug.DebugTestBase.StepFilter.IntelliJStepFilter;
 import java.util.ArrayList;
@@ -23,7 +24,7 @@
   public void testDefaultMethod() throws Throwable {
     // TODO(b/67225390) Dalvik steps into class loader first.
     Assume.assumeTrue("Dalvik suspends in class loader",
-        ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4));
+        ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4));
 
     String debuggeeClass = "DebugInterfaceMethod";
     String parameterName = "msg";
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index c01ef70..b344c8c 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
@@ -69,7 +70,7 @@
     assertNull(appInfo.lookupDirectTarget(method.method));
     assertNotNull(appInfo.lookupStaticTarget(method.method));
 
-    if (ToolHelper.getDexVm() == DexVm.ART_4_4_4) {
+    if (ToolHelper.getDexVm().getVersion() == DexVm.Version.V4_4_4) {
       // Dalvik rejects at verification time instead of producing the
       // expected IncompatibleClassChangeError.
       try {
diff --git a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
index a03f8a5..c8b7803 100644
--- a/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
+++ b/src/test/java/com/android/tools/r8/jasmin/FillBooleanArrayTruncation.java
@@ -30,7 +30,7 @@
 
   private void runTest(JasminBuilder builder, String main) throws Exception {
     String javaResult = runOnJava(builder, main);
-    if (ToolHelper.getDexVm() == DexVm.ART_4_4_4) {
+    if (ToolHelper.getDexVm().getVersion() == DexVm.Version.V4_4_4) {
       // On dalvik the need for truncation is treated as a verification error.
       runOnDalvikCheckVerifyError(builder, main);
       runOnDalvikDxCheckVerifyError(builder, main);
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index a71291d..8e12fc6 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -57,15 +58,15 @@
   }
 
   static boolean isAndroidMOrAbove(DexVm dexVm, Tool tool) {
-    return dexVm.isNewerThan(DexVm.ART_5_1_1);
+    return dexVm.getVersion().isNewerThan(Version.V5_1_1);
   }
 
   static boolean isAndroidNOrAbove(DexVm dexVm, Tool tool) {
-    return dexVm.isNewerThan(DexVm.ART_6_0_1);
+    return dexVm.getVersion().isNewerThan(Version.V6_0_1);
   }
 
   static boolean isAndroidOOrAbove(DexVm dexVm, Tool tool) {
-    return dexVm.isNewerThan(DexVm.ART_7_0_0);
+    return dexVm.getVersion().isNewerThan(Version.V7_0_0);
   }
 
   static boolean isLatestRuntime(DexVm dexVm, Tool tool) {
@@ -208,8 +209,13 @@
           "-cp", System.getProperty("java.class.path") + File.pathSeparator + lib,
           run, pkg + "." + test);
     } else {
+      // TODO(jmhenaff): fix issue with python scripts
+      Assume
+          .assumeTrue("Python script fails because of library names conflicts. Skipping",
+              !ToolHelper.isWindows());
       command = Arrays.asList(
-          RUN_SCRIPT, "--classpath=" + lib, "--version=" + ToolHelper.getDexVm(), test);
+          RUN_SCRIPT, "--classpath=" + lib, "--version=" + ToolHelper.getDexVm().getVersion(),
+          test);
     }
     ProcessBuilder builder = new ProcessBuilder(command);
     ProcessResult result = ToolHelper.runProcess(builder);
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index c7b0c32..d1a2514 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder;
@@ -38,7 +39,7 @@
      */
 
     // TODO(66944616): Can we make this work on Dalvik as well?
-    Assume.assumeTrue("Skipping on 4.4.4", ToolHelper.getDexVm() != ToolHelper.DexVm.ART_4_4_4);
+    Assume.assumeTrue("Skipping on 4.4.4", ToolHelper.getDexVm().getVersion() != Version.V4_4_4);
 
     // Build the second version of LibraryClass
     JasminBuilder compileTimeLibrary = new JasminBuilder();
diff --git a/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java b/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java
index b68c15d..b18dda8 100644
--- a/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
@@ -21,6 +22,7 @@
   @Before
   public void setUp() {
     Assume.assumeTrue(ToolHelper.artSupported());
+    Assume.assumeTrue(ToolHelper.getDexVm().getKind() == Kind.HOST);
   }
 
   @Test
diff --git a/third_party/ddmlib.tar.gz.sha1 b/third_party/ddmlib.tar.gz.sha1
new file mode 100644
index 0000000..6c24762
--- /dev/null
+++ b/third_party/ddmlib.tar.gz.sha1
@@ -0,0 +1 @@
+487d9ad2b151e11904cdfd27dc699debf7e5700a
\ No newline at end of file
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py
index 7cf53b9..600c140 100755
--- a/tools/create_art_tests.py
+++ b/tools/create_art_tests.py
@@ -3,14 +3,16 @@
 # 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.
 
+import os
 from os import makedirs, listdir
 from os.path import join, exists, isdir
 from string import Template, upper
 from sys import exit
 from shutil import rmtree
 
-OUTPUT_DIR = "build/generated/test/java/com/android/tools/r8/art"
-TEST_DIR = "tests/2017-07-27/art"
+OUTPUT_DIR = os.path.join('build', 'generated', 'test', 'java', 'com',
+                          'android', 'tools', 'r8', 'art')
+TEST_DIR = os.path.join('tests', '2017-07-27', 'art')
 TOOLCHAINS = ["dx", "jack", "none"]
 TOOLS = ["r8", "d8"]
 TEMPLATE = Template(
diff --git a/tools/test.py b/tools/test.py
index 7818915..0440f57c 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -16,6 +16,7 @@
 import uuid
 import notify
 
+
 ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1", "4.4.4"]
 BUCKET = 'r8-test-results'
 
@@ -41,6 +42,11 @@
            'all art vm versions (stopping after first failed execution)',
       default="default",
       choices=ALL_ART_VMS + ["all"])
+  result.add_option('--dex_vm_kind',
+                    help='Whether to use host or target version of runtime',
+                    default="host",
+                    nargs=1,
+                    choices=["host", "target"])
   result.add_option('--one_line_per_test',
       help='Print a line before a tests starts and after it ends to stdout.',
       default=False, action='store_true')
@@ -139,7 +145,8 @@
   # Now run tests on selected runtime(s).
   vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
   for art_vm in vms_to_test:
-    return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % art_vm],
+    vm_kind_to_test = "_" + options.dex_vm_kind if art_vm != "default" else ""
+    return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % (art_vm + vm_kind_to_test)],
                                    throw_on_failure=False)
     if return_code != 0:
       if options.archive_failures and os.name != 'nt':