Setup of test parameters for available runtimes.

Bug: 127785410
Change-Id: I932beef5f155ce26439a6385b70d09a1523ba445
diff --git a/build.gradle b/build.gradle
index f82ac3e..a38fd63 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1559,6 +1559,14 @@
         systemProperty 'dex_vm', project.property('dex_vm')
     }
 
+    // Forward vm configurations for test parameters.
+    if (project.hasProperty('dex_vms')) {
+        systemProperty 'dex_vms', project.property('dex_vms')
+    }
+    if (project.hasProperty('cf_vms')) {
+        systemProperty 'cf_vms', project.property('cf_vms')
+    }
+
     if (project.hasProperty('one_line_per_test')) {
         beforeTest { desc ->
             println "Start executing test ${desc.name} [${desc.className}]"
diff --git a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
index 6eae670..39a7dc6 100644
--- a/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/GenerateMainDexListTestBuilder.java
@@ -40,7 +40,7 @@
   }
 
   @Override
-  public GenerateMainDexListRunResult run(Class mainClass)
+  public GenerateMainDexListRunResult run(TestRuntime runtime, String mainClass)
       throws IOException, CompilationFailedException {
     throw new Unimplemented("No support for running with a main class");
   }
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index f7af253..2eaa6cd 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -113,6 +113,13 @@
     return new JvmTestRunResult(builder.build(), result);
   }
 
+  public JvmTestRunResult run(TestRuntime runtime, String mainClass) throws IOException {
+    assert runtime.isCf();
+    assert TestParametersBuilder.isSystemJdk(runtime.asCf().getVm());
+    ProcessResult result = ToolHelper.runJava(classpath, mainClass);
+    return new JvmTestRunResult(builder.build(), result);
+  }
+
   @Override
   public DebugTestConfig debugConfig() {
     return new CfDebugTestConfig().addPaths(classpath);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 80f7f96..05c2009 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -158,6 +158,10 @@
   @Rule
   public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
 
+  public static TestParametersBuilder getTestParameters() {
+    return TestParametersBuilder.builder();
+  }
+
   /**
    * Check if tests should also run Proguard when applicable.
    */
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 858e1bb..5fccb4a 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -33,13 +33,22 @@
     return self();
   }
 
-  public abstract RR run(String mainClass)
+  @Deprecated
+  public abstract RR run(String mainClass) throws IOException, CompilationFailedException;
+
+  public abstract RR run(TestRuntime runtime, String mainClass)
       throws IOException, CompilationFailedException;
 
+  @Deprecated
   public RR run(Class mainClass) throws IOException, CompilationFailedException {
     return run(mainClass.getTypeName());
   }
 
+  public RR run(TestRuntime runtime, Class mainClass)
+      throws IOException, CompilationFailedException {
+    return run(runtime, mainClass.getTypeName());
+  }
+
   public abstract DebugTestConfig debugConfig();
 
   public abstract T addProgramFiles(Collection<Path> files);
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 6883d1d..7fcc59a 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -47,21 +47,36 @@
 
   protected abstract RR createRunResult(ProcessResult result);
 
+  @Deprecated
   public RR run(Class<?> mainClass) throws IOException {
     return run(mainClass.getTypeName());
   }
 
+  @Deprecated
   public RR run(String mainClass) throws IOException {
     switch (getBackend()) {
       case DEX:
-        return runArt(additionalRunClassPath, mainClass);
+        return runArt(null, additionalRunClassPath, mainClass);
       case CF:
-        return runJava(additionalRunClassPath, mainClass);
+        return runJava(null, additionalRunClassPath, mainClass);
       default:
         throw new Unreachable();
     }
   }
 
+  public RR run(TestRuntime runtime, Class<?> mainClass) throws IOException {
+    return run(runtime, mainClass.getTypeName());
+  }
+
+  public RR run(TestRuntime runtime, String mainClass) throws IOException {
+    assert getBackend() == runtime.getBackend();
+    if (runtime.isDex()) {
+      return runArt(runtime.asDex().getVm(), additionalRunClassPath, mainClass);
+    }
+    assert runtime.isCf();
+    return runJava(runtime, additionalRunClassPath, mainClass);
+  }
+
   public CR addRunClasspathFiles(Path... classpath) {
     return addRunClasspathFiles(Arrays.asList(classpath));
   }
@@ -171,7 +186,10 @@
     }
   }
 
-  private RR runJava(List<Path> additionalClassPath, String mainClass) throws IOException {
+  private RR runJava(TestRuntime runtime, List<Path> additionalClassPath, String mainClass)
+      throws IOException {
+    // TODO(b/127785410): Always assume a non-null runtime.
+    assert runtime == null || TestParametersBuilder.isSystemJdk(runtime.asCf().getVm());
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.ClassFile);
     List<Path> classPath = ImmutableList.<Path>builder()
@@ -182,14 +200,15 @@
     return createRunResult(result);
   }
 
-  private RR runArt(List<Path> additionalClassPath, String mainClass) throws IOException {
+  private RR runArt(DexVm vm, List<Path> additionalClassPath, String mainClass) throws IOException {
+    // TODO(b/127785410): Always assume a non-null runtime.
     Path out = state.getNewTempFolder().resolve("out.zip");
     app.writeToZip(out, OutputMode.DexIndexed);
     List<String> classPath = ImmutableList.<String>builder()
         .addAll(additionalClassPath.stream().map(Path::toString).collect(Collectors.toList()))
         .add(out.toString())
         .build();
-    ProcessResult result = ToolHelper.runArtRaw(classPath, mainClass, dummy -> {});
+    ProcessResult result = ToolHelper.runArtRaw(classPath, mainClass, dummy -> {}, vm);
     return createRunResult(result);
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 9e8f74a..5b55075 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -90,6 +90,12 @@
   }
 
   @Override
+  public RR run(TestRuntime runtime, String mainClass)
+      throws IOException, CompilationFailedException {
+    return compile().run(runtime, mainClass);
+  }
+
+  @Override
   public DebugTestConfig debugConfig() {
     // Rethrow exceptions since debug config is usually used in a delayed wrapper which
     // does not declare exceptions.
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
new file mode 100644
index 0000000..041c33e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+
+// Actual test parameters for a specific configuration. Currently just the runtime configuration.
+public class TestParameters {
+
+  private final TestRuntime runtime;
+
+  public TestParameters(TestRuntime runtime) {
+    assert runtime != null;
+    this.runtime = runtime;
+  }
+
+  // Convenience predicates.
+  public boolean isDexRuntime() {
+    return runtime.isDex();
+  }
+
+  public boolean isCfRuntime() {
+    return runtime.isCf();
+  }
+
+  // Access to underlying runtime/wrapper.
+  public TestRuntime getRuntime() {
+    return runtime;
+  }
+
+  // Helper function to get the "backend" for a given runtime target.
+  public Backend getBackend() {
+    return runtime.getBackend();
+  }
+
+  @Override
+  public String toString() {
+    return runtime.toString();
+  }
+
+  public void setMinApiForRuntime(TestCompilerBuilder<?, ?, ?, ?, ?> builder) {
+    if (runtime.isDex()) {
+      builder.setMinApi(runtime.asDex().getMinApiLevel());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
new file mode 100644
index 0000000..1731547
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class TestParametersBuilder {
+
+  // Static computation of VMs configured as available by the testing invocation.
+  private static final List<CfVm> availableCfVms = getAvailableCfVms();
+  private static final List<DexVm> availableDexVms = getAvailableDexVms();
+
+  // Predicate describing which available CF runtimes are applicable to the test.
+  // Built via the methods found below. Default none.
+  private Predicate<CfVm> cfRuntimePredicate = vm -> false;
+
+  // Predicate describing which available DEX runtimes are applicable to the test.
+  // Built via the methods found below. Default none.
+  private Predicate<DexVm.Version> dexRuntimePredicate = vm -> false;
+
+  private TestParametersBuilder() {}
+
+  public static TestParametersBuilder builder() {
+    return new TestParametersBuilder();
+  }
+
+  /** Add specific runtime if available. */
+  public TestParametersBuilder withCfRuntime(CfVm runtime) {
+    cfRuntimePredicate = cfRuntimePredicate.or(vm -> vm == runtime);
+    return this;
+  }
+
+  /** Add all available CF runtimes. */
+  public TestParametersBuilder withCfRuntimes() {
+    cfRuntimePredicate = vm -> true;
+    return this;
+  }
+
+  /** Add all available CF runtimes between {@param startInclusive} and {@param endInclusive}. */
+  public TestParametersBuilder withCfRuntimes(CfVm startInclusive, CfVm endInclusive) {
+    cfRuntimePredicate =
+        cfRuntimePredicate.or(
+            vm -> startInclusive.lessThanOrEqual(vm) && vm.lessThanOrEqual(endInclusive));
+    return this;
+  }
+
+  /** Add all available CF runtimes starting from and including {@param startInclusive}. */
+  public TestParametersBuilder withCfRuntimesStartingFromIncluding(CfVm startInclusive) {
+    cfRuntimePredicate = cfRuntimePredicate.or(vm -> startInclusive.lessThanOrEqual(vm));
+    return this;
+  }
+
+  /** Add all available CF runtimes starting from and excluding {@param startExcluding}. */
+  public TestParametersBuilder withCfRuntimesStartingFromExcluding(CfVm startExcluding) {
+    cfRuntimePredicate = cfRuntimePredicate.or(vm -> startExcluding.lessThan(vm));
+    return this;
+  }
+
+  /** Add all available CF runtimes ending at and including {@param endInclusive}. */
+  public TestParametersBuilder withCfRuntimesEndingAtIncluding(CfVm endInclusive) {
+    cfRuntimePredicate = cfRuntimePredicate.or(vm -> vm.lessThanOrEqual(endInclusive));
+    return this;
+  }
+
+  /** Add all available CF runtimes ending at and excluding {@param endExclusive}. */
+  public TestParametersBuilder withCfRuntimesEndingAtExcluding(CfVm endExclusive) {
+    cfRuntimePredicate = cfRuntimePredicate.or(vm -> vm.lessThan(endExclusive));
+    return this;
+  }
+
+  /** Add all available DEX runtimes. */
+  public TestParametersBuilder withDexRuntimes() {
+    dexRuntimePredicate = vm -> true;
+    return this;
+  }
+
+  /** Add specific runtime if available. */
+  public TestParametersBuilder withDexRuntime(DexVm.Version runtime) {
+    dexRuntimePredicate = dexRuntimePredicate.or(vm -> vm == runtime);
+    return this;
+  }
+
+  /** Add all available CF runtimes between {@param startInclusive} and {@param endInclusive}. */
+  public TestParametersBuilder withDexRuntimes(
+      DexVm.Version startInclusive, DexVm.Version endInclusive) {
+    dexRuntimePredicate =
+        dexRuntimePredicate.or(
+            vm -> startInclusive.isOlderThanOrEqual(vm) && vm.isOlderThanOrEqual(endInclusive));
+    return this;
+  }
+
+  /** Add all available DEX runtimes starting from and including {@param startInclusive}. */
+  public TestParametersBuilder withDexRuntimesStartingFromIncluding(DexVm.Version startInclusive) {
+    dexRuntimePredicate = dexRuntimePredicate.or(vm -> startInclusive.isOlderThanOrEqual(vm));
+    return this;
+  }
+
+  /** Add all available DEX runtimes starting from and excluding {@param startExcluding}. */
+  public TestParametersBuilder withDexRuntimesStartingFromExcluding(DexVm.Version startExcluding) {
+    dexRuntimePredicate =
+        dexRuntimePredicate.or(vm -> vm != startExcluding && startExcluding.isOlderThanOrEqual(vm));
+    return this;
+  }
+
+  /** Add all available DEX runtimes ending at and including {@param endInclusive}. */
+  public TestParametersBuilder withDexRuntimesEndingAtIncluding(DexVm.Version endInclusive) {
+    dexRuntimePredicate = dexRuntimePredicate.or(vm -> vm.isOlderThanOrEqual(endInclusive));
+    return this;
+  }
+
+  /** Add all available DEX runtimes ending at and excluding {@param endExclusive}. */
+  public TestParametersBuilder withDexRuntimesEndingAtExcluding(DexVm.Version endExclusive) {
+    dexRuntimePredicate =
+        dexRuntimePredicate.or(vm -> vm != endExclusive && vm.isOlderThanOrEqual(endExclusive));
+    return this;
+  }
+
+  public Collection<TestParameters> build() {
+    List<TestParameters> parameters = new ArrayList<>();
+    availableCfVms.stream()
+        .filter(TestParametersBuilder::isSupportedJdk)
+        .filter(cfRuntimePredicate)
+        .forEach(vm -> parameters.add(new TestParameters(new CfRuntime(vm))));
+    availableDexVms.stream()
+        .filter(vm -> dexRuntimePredicate.test(vm.getVersion()))
+        .forEach(vm -> parameters.add(new TestParameters(new DexRuntime(vm))));
+    return parameters;
+  }
+
+  // Public method to check that the CF runtime coincides with the system runtime.
+  public static boolean isSystemJdk(CfVm vm) {
+    return vm == TestRuntime.CfVm.JDK8 && isSystemJdkJava8()
+        || vm == TestRuntime.CfVm.JDK9 && isSystemJdkJava9();
+  }
+
+  private static boolean isSystemJdkJava8() {
+    return System.getProperty("java.version").startsWith("8.");
+  }
+
+  private static boolean isSystemJdkJava9() {
+    return System.getProperty("java.version").startsWith("9.");
+  }
+
+  // Currently the only supported VM is the system VM. This should be extended to start supporting
+  // the checked in versions too, making it possible to run tests on more than one JDK at a time.
+  private static boolean isSupportedJdk(CfVm vm) {
+    return isSystemJdk(vm);
+  }
+
+  private static List<CfVm> getAvailableCfVms() {
+    String cfVmsProperty = System.getProperty("cf_vms");
+    if (cfVmsProperty != null) {
+      return Arrays.stream(cfVmsProperty.split(":"))
+          .filter(s -> !s.isEmpty())
+          .map(TestRuntime.CfVm::fromName)
+          .collect(Collectors.toList());
+    } else {
+      return Collections.singletonList(TestRuntime.CfVm.JDK9);
+    }
+  }
+
+  private static List<DexVm> getAvailableDexVms() {
+    String dexVmsProperty = System.getProperty("dex_vms");
+    if (dexVmsProperty != null) {
+      return Arrays.stream(dexVmsProperty.split(":"))
+          .filter(s -> !s.isEmpty())
+          .map(v -> DexVm.fromShortName(v + "_host"))
+          .collect(Collectors.toList());
+    } else {
+      return Arrays.stream(DexVm.Version.values())
+          .map(v -> DexVm.fromShortName(v.toString() + "_host"))
+          .collect(Collectors.toList());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
new file mode 100644
index 0000000..d94a100
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -0,0 +1,143 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+// Base class for the runtime structure in the test parameters.
+public class TestRuntime {
+
+  // Enum describing the possible/supported CF runtimes.
+  public enum CfVm {
+    JDK8("jdk8"),
+    JDK9("jdk9");
+
+    private final String name;
+
+    public static CfVm fromName(String v) {
+      for (CfVm value : CfVm.values()) {
+        if (value.name.equals(v)) {
+          return value;
+        }
+      }
+      throw new Unreachable("Unexpected CfVm name: " + v);
+    }
+
+    CfVm(String name) {
+      this.name = name;
+    }
+
+    public static CfVm first() {
+      return JDK8;
+    }
+
+    public static CfVm last() {
+      return JDK9;
+    }
+
+    public boolean lessThan(CfVm other) {
+      return this.ordinal() < other.ordinal();
+    }
+
+    public boolean lessThanOrEqual(CfVm other) {
+      return this.ordinal() <= other.ordinal();
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+
+  // Wrapper for the DEX runtimes.
+  public static class DexRuntime extends TestRuntime {
+    private final DexVm vm;
+
+    public DexRuntime(DexVm vm) {
+      assert vm != null;
+      this.vm = vm;
+    }
+
+    @Override
+    public boolean isDex() {
+      return true;
+    }
+
+    @Override
+    public DexRuntime asDex() {
+      return this;
+    }
+
+    public DexVm getVm() {
+      return vm;
+    }
+
+    @Override
+    public String toString() {
+      return "dex-" + vm.getVersion().toString();
+    }
+
+    public AndroidApiLevel getMinApiLevel() {
+      return ToolHelper.getMinApiLevelForDexVm(vm);
+    }
+  }
+
+  // Wrapper for the CF runtimes.
+  public static class CfRuntime extends TestRuntime {
+    private final CfVm vm;
+
+    public CfRuntime(CfVm vm) {
+      assert vm != null;
+      this.vm = vm;
+    }
+
+    @Override
+    public boolean isCf() {
+      return true;
+    }
+
+    @Override
+    public CfRuntime asCf() {
+      return this;
+    }
+
+    public CfVm getVm() {
+      return vm;
+    }
+
+    @Override
+    public String toString() {
+      return vm.toString();
+    }
+  }
+
+  public boolean isDex() {
+    return false;
+  }
+
+  public boolean isCf() {
+    return false;
+  }
+
+  public DexRuntime asDex() {
+    return null;
+  }
+
+  public CfRuntime asCf() {
+    return null;
+  }
+
+  public Backend getBackend() {
+    if (isDex()) {
+      return Backend.DEX;
+    }
+    if (isCf()) {
+      return Backend.CF;
+    }
+    throw new Unreachable("Unexpected runtime without backend: " + this);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 6c3af2b..dd8cfc5 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -8,9 +8,11 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,54 +39,56 @@
   }
 
   @Parameters(name = "{0}")
-  public static Backend[] data() {
-    return Backend.values();
+  public static Collection<TestParameters> params() {
+    return getTestParameters().withCfRuntimes().withDexRuntimes().build();
   }
 
-  private final Backend backend;
+  private final TestParameters parameters;
 
-  public DefaultInterfaceMethodTest(Backend backend) {
-    this.backend = backend;
+  public DefaultInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
   }
 
   @Test
   public void testJvm() throws Throwable {
-    Assume.assumeTrue(backend == Backend.CF);
+    Assume.assumeTrue(parameters.isCfRuntime());
     testForJvm()
         .addProgramClasses(LibraryInterface.class, ProgramClass.class)
-        .run(ProgramClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testFullProgram() throws Throwable {
-    testForR8(backend)
+    testForR8(parameters.getBackend())
         .addProgramClasses(LibraryInterface.class, ProgramClass.class)
         .addKeepMainRule(ProgramClass.class)
-        .run(ProgramClass.class)
+        .apply(parameters::setMinApiForRuntime)
+        .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
 
   @Test
   public void testLibraryLinkedWithProgram() throws Throwable {
     R8TestCompileResult libraryResult =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .addProgramClasses(LibraryInterface.class)
             .addKeepRules("-keep class " + LibraryInterface.class.getTypeName() + " { *; }")
+            .apply(parameters::setMinApiForRuntime)
             .compile();
 
     R8TestRunResult result =
-        testForR8(backend)
+        testForR8(parameters.getBackend())
             .noTreeShaking()
-            .noMinification()
             .addProgramClasses(ProgramClass.class)
             .addLibraryClasses(LibraryInterface.class)
             .addApplyMapping(libraryResult.getProguardMap())
+            .apply(parameters::setMinApiForRuntime)
             .compile()
             .addRunClasspathFiles(libraryResult.writeToZip())
-            .run(ProgramClass.class);
+            .run(parameters.getRuntime(), ProgramClass.class);
 
-    if (backend == Backend.DEX && willDesugarDefaultInterfaceMethods()) {
+    if (willDesugarDefaultInterfaceMethods(parameters.getRuntime())) {
       // TODO(b/127779880): The use of the default lambda will fail in case of desugaring.
       result.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
     } else {
@@ -92,7 +96,8 @@
     }
   }
 
-  private static boolean willDesugarDefaultInterfaceMethods() {
-    return ToolHelper.getMinApiLevelForDexVm().getLevel() < AndroidApiLevel.N.getLevel();
+  private static boolean willDesugarDefaultInterfaceMethods(TestRuntime runtime) {
+    return runtime.isDex()
+        && runtime.asDex().getMinApiLevel().getLevel() < AndroidApiLevel.N.getLevel();
   }
 }
diff --git a/tools/test.py b/tools/test.py
index a7e0b98..06aefca 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -257,11 +257,20 @@
   if options.dex_vm == 'all' and options.only_jctf:
     vms_to_test = ["default",  "5.1.1"]
 
+  # The full set of VMs is configured in the first run, then set to empty below.
+  dex_vms_property = ':'.join(vms_to_test)
   for art_vm in vms_to_test:
-    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)
-
+    vm_suffix = "_" + options.dex_vm_kind if art_vm != "default" else ""
+    return_code = gradle.RunGradle(
+        gradle_args + [
+          '-Pdex_vm=%s' % art_vm + vm_suffix,
+          '-Pdex_vms=%s' % dex_vms_property
+        ] +
+        # Only run the CF VMs on the 'default' configuration.
+        ([] if art_vm == "default" else ['-Pcf_vms=']),
+        throw_on_failure=False)
+    # Only run the full set of DEX VMs on the first run.
+    dex_vms_property = ""
     if options.generate_golden_files_to:
       sha1 = '%s' % utils.get_HEAD_sha1()
       with utils.ChangedWorkingDirectory(options.generate_golden_files_to):