Changing JtregRunner to support running tests off device.
diff --git a/libcore/tools/dalvik_jtreg/Android.mk b/libcore/tools/dalvik_jtreg/Android.mk
index 7da7212..2998e0c 100644
--- a/libcore/tools/dalvik_jtreg/Android.mk
+++ b/libcore/tools/dalvik_jtreg/Android.mk
@@ -6,16 +6,19 @@
java/dalvik/jtreg/Adb.java \
java/dalvik/jtreg/Command.java \
java/dalvik/jtreg/CommandFailedException.java \
+ java/dalvik/jtreg/DeviceDalvikVm.java \
java/dalvik/jtreg/Dx.java \
java/dalvik/jtreg/ExpectedResult.java \
+ java/dalvik/jtreg/Harness.java \
java/dalvik/jtreg/Javac.java \
+ java/dalvik/jtreg/JavaVm.java \
java/dalvik/jtreg/JtregRunner.java \
java/dalvik/jtreg/Result.java \
java/dalvik/jtreg/Strings.java \
- java/dalvik/jtreg/TestRun.java \
java/dalvik/jtreg/TestDescriptions.java \
+ java/dalvik/jtreg/TestRun.java \
java/dalvik/jtreg/TestRunner.java \
- java/dalvik/jtreg/TestToDex.java \
+ java/dalvik/jtreg/Vm.java \
java/dalvik/jtreg/XmlReportPrinter.java \
LOCAL_MODULE:= dalvik_jtreg
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java
index 9682a18..14aaadb 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java
@@ -22,8 +22,8 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Callable;
/**
* An out of process executable.
@@ -48,10 +48,18 @@
this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
}
+ public List<String> getArgs() {
+ return Collections.unmodifiableList(args);
+ }
+
static String path(Object... objects) {
return Strings.join(objects, ":");
}
+ static String path(Iterable<?> objects) {
+ return Strings.join(objects, ":");
+ }
+
public synchronized void start() throws IOException {
if (isStarted()) {
throw new IllegalStateException("Already started!");
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/DeviceDalvikVm.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/DeviceDalvikVm.java
new file mode 100644
index 0000000..b49b912
--- /dev/null
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/DeviceDalvikVm.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.jtreg;
+
+import java.io.File;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+/**
+ * Execute tests on a Dalvik VM using an Android device or emulator.
+ */
+final class DeviceDalvikVm extends Vm {
+
+ private static final Logger logger = Logger.getLogger(JtregRunner.class.getName());
+ private final File deviceTemp = new File("/data/jtreg" + UUID.randomUUID());
+
+ private final Adb adb = new Adb();
+ private final File testTemp;
+
+ DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp) {
+ super(debugPort, timeoutSeconds, sdkJar, localTemp);
+ this.testTemp = new File(deviceTemp, "/tests.tmp");
+ }
+
+ @Override public void prepare() {
+ adb.mkdir(deviceTemp);
+ adb.mkdir(testTemp);
+ if (debugPort != null) {
+ adb.forwardTcp(debugPort, debugPort);
+ }
+ super.prepare();
+ }
+
+ @Override protected File postCompile(File classesDirectory, String name) {
+ logger.fine("dex and push " + name);
+
+ // make the local dex
+ File localDex = new File(localTemp, name + ".jar");
+ new Dx().dex(localDex.toString(), classesDirectory);
+
+ // post the local dex to the device
+ File deviceDex = new File(deviceTemp, localDex.getName());
+ adb.push(localDex, deviceDex);
+
+ return deviceDex;
+ }
+
+ @Override public void shutdown() {
+ super.shutdown();
+ adb.rm(deviceTemp);
+ }
+
+ @Override public void buildAndInstall(TestRun testRun) {
+ super.buildAndInstall(testRun);
+
+ File base = new File(deviceTemp, testRun.getQualifiedName());
+ adb.mkdir(base);
+ adb.push(testRun.getTestDescription().getDir(), base);
+ testRun.setUserDir(base);
+ }
+
+ @Override protected VmCommandBuilder newVmCommandBuilder() {
+ return new VmCommandBuilder()
+ .vmCommand("adb", "shell", "dalvikvm")
+ .vmArgs("-Duser.name=root")
+ .vmArgs("-Duser.language=en")
+ .vmArgs("-Duser.region=US")
+ .vmArgs("-Djavax.net.ssl.trustStore=/system/etc/security/cacerts.bks")
+ .temp(testTemp);
+ }
+}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Harness.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Harness.java
new file mode 100644
index 0000000..7898232
--- /dev/null
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Harness.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.jtreg;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Formatter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * Command line interface for running jtreg tests.
+ */
+public final class Harness {
+
+ private final File localTemp;
+ private File sdkJar;
+ private Integer debugPort;
+ private long timeoutSeconds;
+ private Set<File> expectationDirs = new LinkedHashSet<File>();
+ private File xmlReportsDirectory;
+ private String javaHome;
+ private File directoryToScan;
+
+ private Harness() {
+ localTemp = new File("/tmp/" + UUID.randomUUID());
+ timeoutSeconds = 10 * 60; // default is ten minutes
+ sdkJar = new File("/home/dalvik-prebuild/android-sdk-linux/platforms/android-2.0/android.jar");
+ expectationDirs.add(new File("dalvik/libcore/tools/dalvik_jtreg/expectations"));
+ }
+
+ private void prepareLogging() {
+ ConsoleHandler handler = new ConsoleHandler();
+ handler.setLevel(Level.ALL);
+ handler.setFormatter(new Formatter() {
+ @Override public String format(LogRecord r) {
+ return r.getMessage() + "\n";
+ }
+ });
+ Logger logger = Logger.getLogger("dalvik.jtreg");
+ logger.addHandler(handler);
+ logger.setUseParentHandlers(false);
+ }
+
+ private boolean parseArgs(String[] args) throws Exception {
+ if (args.length == 0) {
+ return false;
+ }
+
+ int i = 0;
+ for (; i < args.length - 1; i++) {
+ if ("--debug".equals(args[i])) {
+ debugPort = Integer.valueOf(args[++i]);
+
+ } else if ("--expectations".equals(args[i])) {
+ File expectationDir = new File(args[++i]);
+ if (!expectationDir.isDirectory()) {
+ System.out.println("Invalid expectation directory: " + expectationDir);
+ return false;
+ }
+ expectationDirs.add(expectationDir);
+
+ } else if ("--javaHome".equals(args[i])) {
+ javaHome = args[++i];
+ if (!new File(javaHome, "/bin/java").exists()) {
+ System.out.println("Invalid java home: " + javaHome);
+ return false;
+ }
+
+ } else if ("--timeout-seconds".equals(args[i])) {
+ timeoutSeconds = Long.valueOf(args[++i]);
+
+ } else if ("--sdk".equals(args[i])) {
+ sdkJar = new File(args[++i]);
+ if (!sdkJar.exists()) {
+ System.out.println("Could not find SDK jar: " + sdkJar);
+ return false;
+ }
+
+ } else if ("--verbose".equals(args[i])) {
+ Logger.getLogger("dalvik.jtreg").setLevel(Level.FINE);
+
+ } else if ("--xml-reports-directory".equals(args[i])) {
+ xmlReportsDirectory = new File(args[++i]);
+ if (!xmlReportsDirectory.isDirectory()) {
+ System.out.println("Invalid XML reports directory: " + xmlReportsDirectory);
+ return false;
+ }
+
+ } else {
+ System.out.println("Unrecognized option: " + args[i]);
+ return false;
+ }
+ }
+
+ if (i > args.length - 1) {
+ System.out.println("Missing required test directory option");
+ return false;
+ }
+
+ directoryToScan = new File(args[i]);
+ if (!directoryToScan.isDirectory()) {
+ System.out.println("Invalid test directory: " + directoryToScan);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void printUsage() {
+ System.out.println("Usage: JTRegRunner [options]... <tests directory>");
+ System.out.println();
+ System.out.println(" <tests directory>: a directory to scan for test cases;");
+ System.out.println(" typically this is 'platform_v6/jdk/test' if 'platform_v6'");
+ System.out.println(" contains the sources of a platform implementation.");
+ System.out.println();
+ System.out.println("OPTIONS");
+ System.out.println();
+ System.out.println(" --debug <port>: enable Java debugging on the specified port.");
+ System.out.println(" This port must be free both on the device and on the local");
+ System.out.println(" system.");
+ System.out.println();
+ System.out.println(" --expectations <directory>: use the specified directory when");
+ System.out.println(" looking for test expectations. The directory should include");
+ System.out.println(" <test>.expected files describing expected results.");
+ System.out.println(" Default is: " + expectationDirs);
+ System.out.println();
+ System.out.println(" --javaHome <java_home>: execute the tests on the local workstation");
+ System.out.println(" using the specified java home directory. This does not impact");
+ System.out.println(" which javac gets used. When unset, tests are run on a device");
+ System.out.println(" using adb.");
+ System.out.println();
+ System.out.println(" --sdk <android jar>: the API jar file to compile against.");
+ System.out.println(" Usually this is <SDK>/platforms/android-<X.X>/android.jar");
+ System.out.println(" where <SDK> is the path to an Android SDK path and <X.X> is");
+ System.out.println(" a release version like 1.5.");
+ System.out.println(" Default is: " + sdkJar);
+ System.out.println();
+ System.out.println(" --timeout-seconds <seconds>: maximum execution time of each");
+ System.out.println(" test before the runner aborts it.");
+ System.out.println(" Default is: " + timeoutSeconds);
+ System.out.println();
+ System.out.println(" --xml-reports-directory <path>: directory to emit JUnit-style");
+ System.out.println(" XML test results.");
+ System.out.println();
+ System.out.println(" --verbose: turn on verbose output");
+ System.out.println();
+ }
+
+ private void run() throws Exception {
+ Vm vm = javaHome != null
+ ? new JavaVm(debugPort, timeoutSeconds, sdkJar, localTemp, javaHome)
+ : new DeviceDalvikVm(debugPort, timeoutSeconds, sdkJar, localTemp);
+ JtregRunner jtregRunner = new JtregRunner(localTemp, directoryToScan,
+ vm, expectationDirs, xmlReportsDirectory);
+ jtregRunner.buildAndRunAllTests();
+ vm.shutdown();
+ }
+
+ public static void main(String[] args) throws Exception {
+ Harness harness = new Harness();
+ if (!harness.parseArgs(args)) {
+ harness.printUsage();
+ return;
+ }
+ harness.prepareLogging();
+ harness.parseArgs(args);
+ harness.run();
+ }
+}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JavaVm.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JavaVm.java
new file mode 100644
index 0000000..d9bad33
--- /dev/null
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JavaVm.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.jtreg;
+
+import java.io.File;
+
+/**
+ * A Java virtual machine like Harmony or the RI.
+ */
+final class JavaVm extends Vm {
+
+ private final String javaHome;
+
+ JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar,
+ File localTemp, String javaHome) {
+ super(debugPort, timeoutSeconds, sdkJar, localTemp);
+ this.javaHome = javaHome;
+ }
+
+ @Override protected VmCommandBuilder newVmCommandBuilder() {
+ return new VmCommandBuilder()
+ .vmCommand(javaHome + "/bin/java");
+ }
+}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java
index c6f2700..751757d 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java
@@ -17,55 +17,45 @@
package dalvik.jtreg;
import com.sun.javatest.TestDescription;
+import com.sun.javatest.TestResult;
+import com.sun.javatest.TestResultTable;
+import com.sun.javatest.TestSuite;
+import com.sun.javatest.WorkDirectory;
+import com.sun.javatest.regtest.RegressionTestSuite;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.ConsoleHandler;
-import java.util.logging.Formatter;
import java.util.logging.Level;
-import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* Runs a directory's worth of jtreg tests on a device.
*/
-public final class JtregRunner {
+final class JtregRunner {
private static final Logger logger = Logger.getLogger(JtregRunner.class.getName());
- private final File localTemp = new File("/tmp/" + UUID.randomUUID());
- private final File deviceTemp = new File("/data/jtreg" + UUID.randomUUID());
- private final File testTemp = new File(deviceTemp, "/tests.tmp");
-
- private final Adb adb = new Adb();
+ private final File localTemp;
private final File directoryToScan;
- private final TestToDex testToDex;
- private final ExecutorService outputReaders = Executors.newFixedThreadPool(1);
+ private final Set<File> expectationDirs;
+ private final Vm vm;
+ private final File xmlReportsDirectory;
- private Integer debugPort;
- private Set<File> expectationDirs = new LinkedHashSet<File>();
- private long timeoutSeconds = 10 * 60; // default is ten minutes
- private File xmlReportsDirectory;
-
- private File deviceTestRunner;
-
- public JtregRunner(File sdkJar, File directoryToScan) {
+ public JtregRunner(File localTemp, File directoryToScan, Vm vm,
+ Set<File> expectationDirs, File xmlReportsDirectory) {
+ this.localTemp = localTemp;
this.directoryToScan = directoryToScan;
- this.testToDex = new TestToDex(sdkJar, localTemp);
+ this.expectationDirs = expectationDirs;
+ this.vm = vm;
+ this.xmlReportsDirectory = xmlReportsDirectory;
}
/**
@@ -74,7 +64,7 @@
public void buildAndRunAllTests() throws Exception {
localTemp.mkdirs();
- List<TestDescription> tests = testToDex.findTests(directoryToScan);
+ List<TestDescription> tests = findTests(directoryToScan);
final BlockingQueue<TestRun> readyToRun = new ArrayBlockingQueue<TestRun>(4);
// build and install tests in a background thread. Using lots of
@@ -103,7 +93,7 @@
}
builders.shutdown();
- prepareDevice();
+ vm.prepare();
int unsupportedTests = 0;
@@ -119,7 +109,7 @@
}
if (testRun.isRunnable()) {
- runTest(testRun);
+ vm.runTest(testRun);
}
printResult(testRun);
@@ -137,109 +127,34 @@
}
/**
- * Initializes the temporary directories and test harness necessary to run
- * tests on a device.
+ * Returns the tests in {@code directoryToScan}.
*/
- private void prepareDevice() {
- adb.mkdir(deviceTemp);
- adb.mkdir(testTemp);
- File testRunnerJar = testToDex.writeTestRunnerJar();
- adb.push(testRunnerJar, deviceTemp);
- deviceTestRunner = new File(deviceTemp, testRunnerJar.getName());
- if (debugPort != null) {
- adb.forwardTcp(debugPort, debugPort);
+ List<TestDescription> findTests(File directoryToScan) throws Exception {
+ logger.info("Scanning " + directoryToScan + " for tests.");
+ File workDirectory = new File(localTemp, "JTwork");
+ workDirectory.mkdirs();
+
+ /*
+ * This code is capable of extracting test descriptions using jtreg 4.0
+ * and its bundled copy of jtharness. As a command line tool, jtreg's
+ * API wasn't intended for this style of use. As a consequence, this
+ * code is fragile and may be incompatible with newer versions of jtreg.
+ */
+ TestSuite testSuite = new RegressionTestSuite(directoryToScan);
+ WorkDirectory wd = WorkDirectory.convert(workDirectory, testSuite);
+ TestResultTable resultTable = wd.getTestResultTable();
+
+ List<TestDescription> result = new ArrayList<TestDescription>();
+ for (Iterator i = resultTable.getIterator(); i.hasNext(); ) {
+ TestResult testResult = (TestResult) i.next();
+ result.add(testResult.getDescription());
}
- logger.info("Prepared device.");
+ logger.info("Found " + result.size() + " tests.");
+ return result;
}
- /**
- * Creates a dex file for the given test and push it out to the device.
- *
- * @return true if building and installing completed successfully.
- */
private void buildAndInstall(TestRun testRun) {
- TestDescription testDescription = testRun.getTestDescription();
- String qualifiedName = testRun.getQualifiedName();
- logger.fine("building " + testRun.getQualifiedName());
-
- File base = new File(deviceTemp, qualifiedName);
- adb.mkdir(base);
-
- File dex;
- try {
- dex = testToDex.dexify(testDescription);
- if (dex == null) {
- testRun.setResult(Result.UNSUPPORTED, Collections.<String>emptyList());
- return;
- }
- } catch (CommandFailedException e) {
- testRun.setResult(Result.COMPILE_FAILED, e.getOutputLines());
- return;
- } catch (IOException e) {
- testRun.setResult(Result.ERROR, e);
- return;
- }
-
- logger.fine("installing " + testRun.getQualifiedName());
- adb.push(testDescription.getDir(), base);
- adb.push(dex, deviceTemp);
- testRun.setInstalledFiles(base, new File(deviceTemp, dex.getName()));
- }
-
- /**
- * Runs the specified test on the device.
- */
- private void runTest(TestRun testRun) {
- if (!testRun.isRunnable()) {
- throw new IllegalArgumentException();
- }
-
- Command.Builder builder = new Command.Builder();
- builder.args("adb", "shell", "dalvikvm");
- builder.args("-classpath", Command.path(testRun.getDeviceDex(), deviceTestRunner));
- builder.args("-Duser.dir=" + testRun.getBase());
- builder.args("-Duser.name=root");
- builder.args("-Duser.language=en");
- builder.args("-Duser.region=US");
- builder.args("-Djavax.net.ssl.trustStore=/system/etc/security/cacerts.bks");
- builder.args("-Djava.io.tmpdir=" + testTemp);
- if (debugPort != null) {
- builder.args("-Xrunjdwp:transport=dt_socket,address="
- + debugPort + ",server=y,suspend=y");
- }
- builder.args("dalvik.jtreg.TestRunner");
- final Command command = builder.build();
-
- try {
- command.start();
-
- // run on a different thread to allow a timeout
- List<String> output = outputReaders.submit(new Callable<List<String>>() {
- public List<String> call() throws Exception {
- return command.gatherOutput();
- }
- }).get(timeoutSeconds, TimeUnit.SECONDS);
-
- if (output.isEmpty()) {
- testRun.setResult(Result.ERROR,
- Collections.singletonList("No output returned!"));
- return;
- }
-
- Result result = "SUCCESS".equals(output.get(output.size() - 1))
- ? Result.SUCCESS
- : Result.EXEC_FAILED;
- testRun.setResult(result, output.subList(0, output.size() - 1));
- } catch (TimeoutException e) {
- testRun.setResult(Result.EXEC_TIMEOUT, e);
- } catch (Exception e) {
- testRun.setResult(Result.ERROR,
- Collections.singletonList("Exceeded timeout! (" + timeoutSeconds + "s)"));
- } finally {
- if (command.isStarted()) {
- command.getProcess().destroy(); // to release the output reader
- }
- }
+ vm.buildAndInstall(testRun);
}
private void printResult(TestRun testRun) {
@@ -275,103 +190,4 @@
logger.info(" " + output);
}
}
-
- private void shutdown() {
- adb.rm(deviceTemp);
- outputReaders.shutdown();
- }
-
- public static void main(String[] args) throws Exception {
- if (args.length < 2) {
- System.out.println("Usage: JTRegRunner [options]... <android jar> <tests directory>");
- System.out.println();
- System.out.println(" <android jar>: the API jar file to compile against. Usually");
- System.out.println(" this is <SDK>/platforms/android-<X.X>/android.jar where");
- System.out.println(" <SDK> is the path to an Android SDK path and <X.X> is a");
- System.out.println(" release version like 1.5.");
- System.out.println();
- System.out.println(" <tests directory>: a directory to scan for test cases;");
- System.out.println(" typically this is 'platform_v6/jdk/test' if 'platform_v6'");
- System.out.println(" contains the sources of a platform implementation.");
- System.out.println();
- System.out.println("OPTIONS");
- System.out.println();
- System.out.println(" --debug <port>: enable Java debugging on the specified port.");
- System.out.println(" This port must be free both on the device and on the local");
- System.out.println(" system.");
- System.out.println();
- System.out.println(" --expectations <directory>: use the specified directory when");
- System.out.println(" looking for test expectations. The directory should include");
- System.out.println(" <test>.expected files describing expected results.");
- System.out.println();
- System.out.println(" --timeout-seconds <seconds>: maximum execution time of each");
- System.out.println(" test before the runner aborts it.");
- System.out.println();
- System.out.println(" --xml-reports-directory <path>: directory to emit JUnit-style");
- System.out.println(" XML test results.");
- System.out.println();
- System.out.println(" --verbose: turn on verbose output");
- System.out.println();
- return;
- }
-
- prepareLogging();
-
- File sdkJar = new File(args[args.length - 2]);
- if (!sdkJar.exists()) {
- throw new RuntimeException("Could not find SDK jar: " + sdkJar);
- }
-
- File directoryToScan = new File(args[args.length - 1]);
- if (!directoryToScan.isDirectory()) {
- throw new RuntimeException("Invalid test directory: " + directoryToScan);
- }
-
- JtregRunner jtregRunner = new JtregRunner(sdkJar, directoryToScan);
-
- for (int i = 0; i < args.length - 2; i++) {
- if ("--debug".equals(args[i])) {
- jtregRunner.debugPort = Integer.valueOf(args[++i]);
-
- } else if ("--expectations".equals(args[i])) {
- File expectationDir = new File(args[++i]);
- if (!expectationDir.isDirectory()) {
- throw new RuntimeException("Invalid expectation directory: " + directoryToScan);
- }
- jtregRunner.expectationDirs.add(expectationDir);
-
- } else if ("--timeout-seconds".equals(args[i])) {
- jtregRunner.timeoutSeconds = Long.valueOf(args[++i]);
-
- } else if ("--verbose".equals(args[i])) {
- Logger.getLogger("dalvik.jtreg").setLevel(Level.FINE);
-
- } else if ("--xml-reports-directory".equals(args[i])) {
- jtregRunner.xmlReportsDirectory = new File(args[++i]);
- if (!jtregRunner.xmlReportsDirectory.isDirectory()) {
- throw new RuntimeException("Invalid XML reports directory: "
- + jtregRunner.xmlReportsDirectory);
- }
-
- } else {
- throw new RuntimeException("Unrecognized option: " + args[i]);
- }
- }
-
- jtregRunner.buildAndRunAllTests();
- jtregRunner.shutdown();
- }
-
- private static void prepareLogging() {
- ConsoleHandler handler = new ConsoleHandler();
- handler.setLevel(Level.ALL);
- handler.setFormatter(new Formatter() {
- @Override public String format(LogRecord r) {
- return r.getMessage() + "\n";
- }
- });
- Logger logger = Logger.getLogger("dalvik.jtreg");
- logger.addHandler(handler);
- logger.setUseParentHandlers(false);
- }
}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java
index feb919a..e49967e 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java
@@ -25,7 +25,14 @@
import java.util.List;
/**
- * A test run and its outcome.
+ * A test run and its outcome. This class tracks the complete lifecycle of a
+ * single test run:
+ * <ol>
+ * <li>the test identity (qualified name)
+ * <li>the test source code (test description)
+ * <li>the code to execute (user dir, test classes)
+ * <li>the result of execution (result, output lines)
+ * </ol>
*/
public final class TestRun {
@@ -33,8 +40,8 @@
private final String qualifiedName;
private final ExpectedResult expectedResult;
- private File base;
- private File deviceDex;
+ private File userDir;
+ private File testClasses;
private Result result;
private List<String> outputLines;
@@ -56,38 +63,33 @@
}
/**
- * Initializes the on-device base directory from which the test program
- * shall be executed, and the dex file containing that program.
+ * Initializes the directory from which local files can be read by the test.
*/
- public void setInstalledFiles(File base, File deviceDex) {
- if (this.base != null) {
- throw new IllegalStateException();
- }
+ public void setUserDir(File base) {
+ this.userDir = base;
+ }
- this.base = base;
- this.deviceDex = deviceDex;
+ public File getUserDir() {
+ return userDir;
}
/**
- * Returns true if this test is ready for execution on a device.
+ * Initializes the path to the jar file or directory containing test
+ * classes.
+ */
+ public void setTestClasses(File classes) {
+ this.testClasses = classes;
+ }
+
+ public File getTestClasses() {
+ return testClasses;
+ }
+
+ /**
+ * Returns true if this test is ready for execution.
*/
public boolean isRunnable() {
- return base != null && deviceDex != null;
- }
-
- /**
- * Returns the test's base directory, from which local files can be read by
- * the test.
- */
- public File getBase() {
- return base;
- }
-
- /**
- * Returns the jar file containing the test code.
- */
- public File getDeviceDex() {
- return deviceDex;
+ return userDir != null && testClasses != null;
}
public void setResult(Result result, Throwable e) {
@@ -121,5 +123,4 @@
public ExpectedResult getExpectedResult() {
return expectedResult;
}
-
}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRunner.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRunner.java
index f1b5577..389813f 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRunner.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRunner.java
@@ -23,7 +23,7 @@
import java.util.Properties;
/**
- * Runs a jtreg test that was prepared with {@link TestToDex}.
+ * Runs a jtreg test.
*/
public final class TestRunner {
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestToDex.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestToDex.java
deleted file mode 100644
index 6badf34..0000000
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestToDex.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package dalvik.jtreg;
-
-import com.sun.javatest.TestDescription;
-import com.sun.javatest.TestResult;
-import com.sun.javatest.TestResultTable;
-import com.sun.javatest.TestSuite;
-import com.sun.javatest.WorkDirectory;
-import com.sun.javatest.regtest.RegressionTestSuite;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Properties;
-import java.util.logging.Logger;
-import java.util.regex.Pattern;
-
-/**
- * Scans a directory of jtreg tests and creates Dalvik-friendly {@code .jar}
- * files for them. These tests can be executed by {@link TestRunner}. Because of
- * the heavy use of the default package by jtreg tests, it is not generally
- * possible to run multiple tests in the same dalvik VM.
- */
-final class TestToDex {
-
- private static final String DALVIK_JTREG_HOME
- = "dalvik/libcore/tools/dalvik_jtreg";
- private static final File JTREG_JAR
- = new File(DALVIK_JTREG_HOME + "/lib/jtreg.jar");
- private static final File TEST_RUNNER_JAVA
- = new File(DALVIK_JTREG_HOME + "/java/dalvik/jtreg/TestRunner.java");
-
- private static final Logger logger = Logger.getLogger(TestToDex.class.getName());
-
- private final Pattern JAVA_TEST_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");
-
- private final File sdkJar;
- private final File temp;
-
- TestToDex(File sdkJar, File temp) {
- this.sdkJar = sdkJar;
- this.temp = temp;
- }
-
- /**
- * Creates a testrunner jar that can execute the packaged tests.
- */
- File writeTestRunnerJar() {
- File base = new File(temp, "testrunner");
- base.mkdirs();
-
- new Javac()
- .destination(base)
- .compile(TEST_RUNNER_JAVA);
-
- File output = new File(temp, "testrunner.jar");
- new Dx().dex(output.toString(), base);
- return output;
- }
-
- /**
- * Writes a Dalvik-friendly {@code .jar} for the described test.
- *
- * @return the path of the constructed {@code .jar}, or {@code null} if the
- * test cannot be converted to Dex (presumably because it is not of
- * the right type).
- * @throws CommandFailedException if javac fails
- */
- File dexify(TestDescription testDescription) throws IOException {
- String qualifiedName = TestDescriptions.qualifiedName(testDescription);
-
- if (!JAVA_TEST_PATTERN.matcher(testDescription.getFile().toString()).find()) {
- return null;
- }
-
- File jarContents = new File(temp, qualifiedName);
- jarContents.mkdirs();
-
- // write a test descriptor
- Properties properties = TestDescriptions.toProperties(testDescription);
- FileOutputStream propertiesOut = new FileOutputStream(
- new File(jarContents, TestRunner.TEST_PROPERTIES_FILE));
- properties.store(propertiesOut, "generated by " + getClass().getName());
- propertiesOut.close();
-
- new Javac()
- .bootClasspath(sdkJar)
- .classpath(testDescription.getDir(), JTREG_JAR)
- .sourcepath(testDescription.getDir())
- .destination(jarContents)
- .compile(testDescription.getFile());
-
- File output = new File(temp, qualifiedName + ".jar");
- new Dx().dex(output.toString(), jarContents);
- return output;
- }
-
- /**
- * Scans {@code directoryToScan} for test cases, using JTHarness + jtreg
- * behind the scenes.
- */
- List<TestDescription> findTests(File directoryToScan) throws Exception {
- logger.info("Scanning " + directoryToScan + " for tests.");
- File workDirectory = new File(temp, "JTwork");
- workDirectory.mkdirs();
-
- /*
- * This code is capable of extracting test descriptions using jtreg 4.0
- * and its bundled copy of jtharness. As a command line tool, jtreg's
- * API wasn't intended for this style of use. As a consequence, this
- * code is fragile and may be incompatible with newer versions of jtreg.
- */
- TestSuite testSuite = new RegressionTestSuite(directoryToScan);
- WorkDirectory wd = WorkDirectory.convert(workDirectory, testSuite);
- TestResultTable resultTable = wd.getTestResultTable();
-
- List<TestDescription> result = new ArrayList<TestDescription>();
- for (Iterator i = resultTable.getIterator(); i.hasNext(); ) {
- TestResult testResult = (TestResult) i.next();
- result.add(testResult.getDescription());
- }
- logger.info("Found " + result.size() + " tests.");
- return result;
- }
-}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Vm.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Vm.java
new file mode 100644
index 0000000..6c2262e
--- /dev/null
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Vm.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.jtreg;
+
+import com.sun.javatest.TestDescription;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+
+/**
+ * A Java-like virtual machine for compiling and running tests.
+ */
+public abstract class Vm {
+
+ static final String DALVIK_JTREG_HOME = "dalvik/libcore/tools/dalvik_jtreg";
+ static final File TEST_RUNNER_JAVA = new File(
+ DALVIK_JTREG_HOME + "/java/dalvik/jtreg/TestRunner.java");
+ private final Pattern JAVA_TEST_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");
+ private static final File JTREG_JAR
+ = new File(DALVIK_JTREG_HOME + "/lib/jtreg.jar");
+
+ private static final Logger logger = Logger.getLogger(Vm.class.getName());
+
+ protected final ExecutorService outputReaders = Executors.newFixedThreadPool(1);
+
+ protected final Integer debugPort;
+ protected final long timeoutSeconds;
+ protected final File sdkJar;
+ protected final File localTemp;
+
+ /** The path to the test runner's compiled classes (directory or jar). */
+ private File testRunnerClasses;
+
+ Vm(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp) {
+ this.debugPort = debugPort;
+ this.timeoutSeconds = timeoutSeconds;
+ this.sdkJar = sdkJar;
+ this.localTemp = localTemp;
+ }
+
+ /**
+ * Initializes the temporary directories and test harness necessary to run
+ * tests.
+ */
+ public void prepare() {
+ testRunnerClasses = compileTestRunner();
+ }
+
+ private File compileTestRunner() {
+ logger.fine("build testrunner");
+
+ File base = new File(localTemp, "testrunner");
+ base.mkdirs();
+ new Javac()
+ .destination(base)
+ .compile(TEST_RUNNER_JAVA);
+ return postCompile(base, "testrunner");
+ }
+
+ /**
+ * Cleans up after all test runs have completed.
+ */
+ public void shutdown() {
+ outputReaders.shutdown();
+ }
+
+ /**
+ * Compiles classes for the given test and makes them ready for execution.
+ * If the test could not be compiled successfully, it will be updated with
+ * the appropriate test result.
+ */
+ public void buildAndInstall(TestRun testRun) {
+ TestDescription testDescription = testRun.getTestDescription();
+ logger.fine("build " + testRun.getQualifiedName());
+
+ File testClasses;
+ try {
+ testClasses = compileTest(testDescription);
+ if (testClasses == null) {
+ testRun.setResult(Result.UNSUPPORTED, Collections.<String>emptyList());
+ return;
+ }
+ } catch (CommandFailedException e) {
+ testRun.setResult(Result.COMPILE_FAILED, e.getOutputLines());
+ return;
+ } catch (IOException e) {
+ testRun.setResult(Result.ERROR, e);
+ return;
+ }
+ testRun.setTestClasses(testClasses);
+ testRun.setUserDir(testDescription.getDir());
+ }
+
+ /**
+ * Compiles the classes for the described test.
+ *
+ * @return the path to the compiled classes (directory or jar), or {@code
+ * null} if the test could not be compiled.
+ * @throws CommandFailedException if javac fails
+ */
+ private File compileTest(TestDescription testDescription) throws IOException {
+ String qualifiedName = TestDescriptions.qualifiedName(testDescription);
+
+ if (!JAVA_TEST_PATTERN.matcher(testDescription.getFile().toString()).find()) {
+ return null;
+ }
+
+ File base = new File(localTemp, qualifiedName);
+ base.mkdirs();
+
+ // write a test descriptor
+ Properties properties = TestDescriptions.toProperties(testDescription);
+ FileOutputStream propertiesOut = new FileOutputStream(
+ new File(base, TestRunner.TEST_PROPERTIES_FILE));
+ properties.store(propertiesOut, "generated by " + getClass().getName());
+ propertiesOut.close();
+
+ new Javac()
+ .bootClasspath(sdkJar)
+ .classpath(testDescription.getDir(), JTREG_JAR)
+ .sourcepath(testDescription.getDir())
+ .destination(base)
+ .compile(testDescription.getFile());
+ return postCompile(base, qualifiedName);
+ }
+
+ /**
+ * Runs the test, and updates its test result.
+ */
+ public void runTest(TestRun testRun) {
+ if (!testRun.isRunnable()) {
+ throw new IllegalArgumentException();
+ }
+
+ final Command command = newVmCommandBuilder()
+ .classpath(testRun.getTestClasses(), testRunnerClasses)
+ .userDir(testRun.getUserDir())
+ .debugPort(debugPort)
+ .mainClass("dalvik.jtreg.TestRunner")
+ .build();
+
+ logger.fine("executing " + command.getArgs());
+
+ try {
+ command.start();
+
+ // run on a different thread to allow a timeout
+ List<String> output = outputReaders.submit(new Callable<List<String>>() {
+ public List<String> call() throws Exception {
+ return command.gatherOutput();
+ }
+ }).get(timeoutSeconds, TimeUnit.SECONDS);
+
+ if (output.isEmpty()) {
+ testRun.setResult(Result.ERROR,
+ Collections.singletonList("No output returned!"));
+ return;
+ }
+
+ Result result = "SUCCESS".equals(output.get(output.size() - 1))
+ ? Result.SUCCESS
+ : Result.EXEC_FAILED;
+ testRun.setResult(result, output.subList(0, output.size() - 1));
+ } catch (TimeoutException e) {
+ testRun.setResult(Result.EXEC_TIMEOUT,
+ Collections.singletonList("Exceeded timeout! (" + timeoutSeconds + "s)"));
+ } catch (Exception e) {
+ testRun.setResult(Result.ERROR, e);
+ } finally {
+ if (command.isStarted()) {
+ command.getProcess().destroy(); // to release the output reader
+ }
+ }
+ }
+
+ /**
+ * Returns a VM for test execution.
+ */
+ protected VmCommandBuilder newVmCommandBuilder() {
+ return new VmCommandBuilder();
+ }
+
+ /**
+ * Hook method called after each compilation.
+ *
+ * @param classesDirectory the compiled classes
+ * @param name the name of this compilation unit. Usually a qualified test
+ * name like java.lang.Math.PowTests.
+ * @return the new result file.
+ */
+ protected File postCompile(File classesDirectory, String name) {
+ return classesDirectory;
+ }
+
+ /**
+ * Builds a virtual machine command.
+ */
+ public static class VmCommandBuilder {
+ private File temp;
+ private List<File> classpath = new ArrayList<File>();
+ private File userDir;
+ private Integer debugPort;
+ private String mainClass;
+ private List<String> vmCommand = Collections.singletonList("java");
+ private List<String> vmArgs = Collections.emptyList();
+
+ public VmCommandBuilder vmCommand(String... vmCommand) {
+ this.vmCommand = Arrays.asList(vmCommand.clone());
+ return this;
+ }
+
+ public VmCommandBuilder temp(File temp) {
+ this.temp = temp;
+ return this;
+ }
+
+ public VmCommandBuilder classpath(File... elements) {
+ this.classpath.addAll(Arrays.asList(elements));
+ return this;
+ }
+
+ public VmCommandBuilder userDir(File userDir) {
+ this.userDir = userDir;
+ return this;
+ }
+
+ public VmCommandBuilder debugPort(Integer debugPort) {
+ this.debugPort = debugPort;
+ return this;
+ }
+
+ public VmCommandBuilder mainClass(String mainClass) {
+ this.mainClass = mainClass;
+ return this;
+ }
+
+ public VmCommandBuilder vmArgs(String... vmArgs) {
+ this.vmArgs = Arrays.asList(vmArgs.clone());
+ return this;
+ }
+
+ public Command build() {
+ Command.Builder builder = new Command.Builder();
+ builder.args(vmCommand);
+ builder.args("-classpath", Command.path(classpath));
+ builder.args("-Duser.dir=" + userDir);
+
+ if (temp != null) {
+ builder.args("-Djava.io.tmpdir=" + temp);
+ }
+
+ if (debugPort != null) {
+ builder.args("-Xrunjdwp:transport=dt_socket,address="
+ + debugPort + ",server=y,suspend=y");
+ }
+
+ builder.args(vmArgs);
+ builder.args(mainClass);
+
+ return builder.build();
+ }
+ }
+}