java/dalvik/runner/CaliperFinder.java \
java/dalvik/runner/CaliperRunner.java \
java/dalvik/runner/Classpath.java \
+ java/dalvik/runner/CodeFinder.java \
java/dalvik/runner/Command.java \
java/dalvik/runner/CommandFailedException.java \
java/dalvik/runner/DalvikRunner.java \
java/dalvik/runner/Javac.java \
java/dalvik/runner/JtregFinder.java \
java/dalvik/runner/JtregRunner.java \
+ java/dalvik/runner/MainFinder.java \
+ java/dalvik/runner/MainRunner.java \
+ java/dalvik/runner/NamingPatternCodeFinder.java \
java/dalvik/runner/Result.java \
java/dalvik/runner/Strings.java \
java/dalvik/runner/TestRun.java \
- java/dalvik/runner/TestFinder.java \
java/dalvik/runner/TestRunner.java \
java/dalvik/runner/Threads.java \
java/dalvik/runner/Vm.java \
* Create {@link TestRun}s for {@code .java} files with Caliper benchmarks in
* them.
*/
-class CaliperFinder extends TestFinder {
+class CaliperFinder extends NamingPatternCodeFinder {
@Override protected boolean matches(File file) {
return file.getName().endsWith("Benchmark.java");
--- /dev/null
+/*
+ * 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.runner;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * A strategy for finding runnable things in a directory.
+ */
+public interface CodeFinder {
+
+ /**
+ * Returns all test runs in the given file or directory. If the returned set
+ * is empty, no executable code of this kind were found.
+ */
+ public Set<TestRun> findTests(File file);
+}
package dalvik.runner;
import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
private final Logger logger = Logger.getLogger(Command.class.getName());
private final List<String> args;
+ private final File workingDirectory;
private final boolean permitNonZeroExitStatus;
private Process process;
Command(List<String> args) {
this.args = new ArrayList<String>(args);
+ this.workingDirectory = null;
this.permitNonZeroExitStatus = false;
}
private Command(Builder builder) {
this.args = new ArrayList<String>(builder.args);
+ this.workingDirectory = builder.workingDirectory;
this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
}
logger.fine("executing " + Strings.join(args, " "));
- process = new ProcessBuilder()
+ ProcessBuilder processBuilder = new ProcessBuilder()
.command(args)
- .redirectErrorStream(true)
- .start();
+ .redirectErrorStream(true);
+ if (workingDirectory != null) {
+ processBuilder.directory(workingDirectory);
+ }
+
+ process = processBuilder.start();
}
public boolean isStarted() {
static class Builder {
private final List<String> args = new ArrayList<String>();
+ private File workingDirectory;
private boolean permitNonZeroExitStatus = false;
public Builder args(String... args) {
return this;
}
+ /**
+ * Sets the working directory from which the command will be executed.
+ * This must be a <strong>local</strong> directory; Commands run on
+ * remote devices (ie. via {@code adb shell}) require a local working
+ * directory.
+ */
+ public Builder workingDirectory(File workingDirectory) {
+ this.workingDirectory = workingDirectory;
+ return this;
+ }
+
public Builder permitNonZeroExitStatus() {
permitNonZeroExitStatus = true;
return this;
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
? new JavaVm(debugPort, timeoutSeconds, sdkJar, localTemp, javaHome, clean)
: new DeviceDalvikVm(debugPort, timeoutSeconds, sdkJar, localTemp,
clean, deviceRunnerDir);
- JtregFinder jtregFinder = new JtregFinder(localTemp);
- JUnitFinder jUnitFinder = new JUnitFinder();
- CaliperFinder caliperFinder = new CaliperFinder();
+ List<CodeFinder> codeFinders = Arrays.asList(
+ new JtregFinder(localTemp),
+ new JUnitFinder(),
+ new CaliperFinder(),
+ new MainFinder());
Driver driver = new Driver(localTemp,
- vm, expectationFiles, xmlReportsDirectory, jtregFinder,
- jUnitFinder, caliperFinder);
+ vm, expectationFiles, xmlReportsDirectory, codeFinders);
driver.loadExpectations();
driver.buildAndRunAllTests(testFiles);
vm.shutdown();
}
}
- @Override public void buildAndInstall(TestRun testRun) {
- super.buildAndInstall(testRun);
-
+ @Override protected void prepareUserDir(TestRun testRun) {
File testClassesDirOnDevice = testClassesDirOnDevice(testRun);
adb.mkdir(testClassesDirOnDevice);
adb.push(testRun.getTestDirectory(), testClassesDirOnDevice);
return new File(runnerDir, testRun.getQualifiedName());
}
- @Override protected VmCommandBuilder newVmCommandBuilder() {
+ @Override protected VmCommandBuilder newVmCommandBuilder(
+ File workingDirectory) {
+ // ignore the working directory; it's device-local and we can't easily
+ // set the working directory for commands run via adb shell.
return new VmCommandBuilder()
.vmCommand("adb", "shell", "dalvikvm")
.vmArgs("-Duser.name=root")
private final File localTemp;
private final Set<File> expectationFiles;
- private final JtregFinder jtregFinder;
- private final JUnitFinder junitFinder;
- private final CaliperFinder caliperFinder;
+ private final List<CodeFinder> codeFinders;
private final Vm vm;
private final File xmlReportsDirectory;
private final Map<String, ExpectedResult> expectedResults = new HashMap<String, ExpectedResult>();
private int unsupportedTests = 0;
public Driver(File localTemp, Vm vm, Set<File> expectationFiles,
- File xmlReportsDirectory, JtregFinder jtregFinder,
- JUnitFinder junit, CaliperFinder caliperFinder) {
+ File xmlReportsDirectory, List<CodeFinder> codeFinders) {
this.localTemp = localTemp;
this.expectationFiles = expectationFiles;
this.vm = vm;
this.xmlReportsDirectory = xmlReportsDirectory;
- this.jtregFinder = jtregFinder;
- this.junitFinder = junit;
- this.caliperFinder = caliperFinder;
+ this.codeFinders = codeFinders;
}
public void loadExpectations() throws IOException {
for (File testFile : testFiles) {
Set<TestRun> testsForFile = Collections.emptySet();
- if (testFile.isDirectory()) {
- testsForFile = jtregFinder.findTests(testFile);
- logger.fine("found " + testsForFile.size() + " jtreg tests for " + testFile);
- }
- if (testsForFile.isEmpty()) {
- testsForFile = junitFinder.findTests(testFile);
- logger.fine("found " + testsForFile.size() + " JUnit tests for " + testFile);
- }
- if (testsForFile.isEmpty()) {
- testsForFile = caliperFinder.findTests(testFile);
- logger.fine("found " + testsForFile.size() + " Caliper benchmarks for " + testFile);
+ for (CodeFinder codeFinder : codeFinders) {
+ testsForFile = codeFinder.findTests(testFile);
+
+ // break as soon as we find any match. We don't need multiple
+ // matches for the same file, since that would run it twice.
+ if (!testsForFile.isEmpty()) {
+ break;
+ }
}
+
tests.addAll(testsForFile);
}
/**
* Create {@link TestRun}s for {@code .java} files with JUnit tests in them.
*/
-class JUnitFinder extends TestFinder {
+class JUnitFinder extends NamingPatternCodeFinder {
@Override protected boolean matches(File file) {
return file.getName().endsWith("Test.java");
import java.io.File;
/**
- * A Java virtual machine like Harmony or the RI.
+ * A local Java virtual machine like Harmony or the RI.
*/
final class JavaVm extends Vm {
this.javaHome = javaHome;
}
- @Override protected VmCommandBuilder newVmCommandBuilder() {
+ @Override protected VmCommandBuilder newVmCommandBuilder(
+ File workingDirectory) {
return new VmCommandBuilder()
- .vmCommand(javaHome + "/bin/java");
+ .vmCommand(javaHome + "/bin/java")
+ .workingDir(workingDirectory);
}
}
import com.sun.javatest.regtest.RegressionTestSuite;
import java.io.File;
-import java.io.FileNotFoundException;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Create {@link TestRun}s for {@code .java} files with jtreg tests in them.
*/
-class JtregFinder {
+class JtregFinder implements CodeFinder {
// TODO: add support for the @library directive, as seen in
// test/com/sun/crypto/provider/Cipher/AES/TestKATForECB_VT.java
/**
* Returns the tests in {@code directoryToScan}.
*/
- public Set<TestRun> findTests(File directoryToScan)
- throws TestSuite.Fault, WorkDirectory.InitializationFault,
- FileNotFoundException, WorkDirectory.WorkDirectoryExistsFault,
- WorkDirectory.BadDirectoryFault, TestResult.Fault {
- logger.fine("scanning " + directoryToScan + " for jtreg 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();
-
- Set<TestRun> result = new LinkedHashSet<TestRun>();
- for (Iterator i = resultTable.getIterator(); i.hasNext(); ) {
- TestResult testResult = (TestResult) i.next();
- TestDescription description = testResult.getDescription();
- String qualifiedName = qualifiedName(description);
- String suiteName = suiteName(description);
- String testName = description.getName();
- String testClass = description.getName();
- result.add(new TestRun(description.getDir(), description.getFile(),
- testClass, suiteName, testName, qualifiedName,
- description.getTitle(), JtregRunner.class));
+ public Set<TestRun> findTests(File directoryToScan) {
+ // for now, jtreg doesn't know how to scan anything but directories
+ if (!directoryToScan.isDirectory()) {
+ return Collections.emptySet();
+ }
+
+ try {
+ logger.fine("scanning " + directoryToScan + " for jtreg 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();
+
+ Set<TestRun> result = new LinkedHashSet<TestRun>();
+ for (Iterator i = resultTable.getIterator(); i.hasNext(); ) {
+ TestResult testResult = (TestResult) i.next();
+ TestDescription description = testResult.getDescription();
+ String qualifiedName = qualifiedName(description);
+ String suiteName = suiteName(description);
+ String testName = description.getName();
+ String testClass = description.getName();
+ result.add(new TestRun(description.getDir(), description.getFile(),
+ testClass, suiteName, testName, qualifiedName,
+ description.getTitle(), JtregRunner.class));
+ }
+ return result;
+ } catch (Exception jtregFailure) {
+ // jtreg shouldn't fail in practice
+ throw new RuntimeException(jtregFailure);
}
- return result;
}
/**
--- /dev/null
+/*
+ * 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.runner;
+
+import java.io.File;
+
+/**
+ * Create {@link TestRun}s for {@code .java} files with main methods in them.
+ */
+class MainFinder extends NamingPatternCodeFinder {
+
+ @Override protected boolean matches(File file) {
+ return file.getName().endsWith(".java");
+ }
+
+ @Override protected String testName(File file) {
+ return "main";
+ }
+
+ @Override protected Class<? extends TestRunner> runnerClass() {
+ return MainRunner.class;
+ }
+}
--- /dev/null
+/*
+ * 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.runner;
+
+import java.lang.reflect.Method;
+
+/**
+ * Runs a Java class with a main method.
+ */
+public final class MainRunner extends TestRunner {
+
+ @Override public boolean test() {
+ try {
+ Method mainMethod = Class.forName(className)
+ .getDeclaredMethod("main", String[].class);
+ mainMethod.invoke(null, new Object[] { new String[0] });
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return false; // always print main method output
+ }
+
+ public static void main(String[] args) throws Exception {
+ new MainRunner().run();
+ }
+}
import java.util.regex.Pattern;
/**
- * A pluggable strategy for converting files into test runs.
+ * A code finder that traverses through the directory tree looking for matching
+ * naming patterns.
*/
-abstract class TestFinder {
+abstract class NamingPatternCodeFinder implements CodeFinder {
+
+ private final String PACKAGE_PATTERN = "(?m)^\\s*package\\s+(\\S+)\\s*;";
+
+ private final String TYPE_DECLARATION_PATTERN
+ = "(?m)\\b(?:public|private)\\s+(?:interface|class|enum)\\b";
public Set<TestRun> findTests(File testDirectory) {
Set<TestRun> result = new LinkedHashSet<TestRun>();
// declaration inside the file.
try {
String content = Strings.readFile(file);
- Pattern packagePattern = Pattern.compile("(?m)^\\s*package\\s+(\\S+)\\s*;");
+ Pattern packagePattern = Pattern.compile(PACKAGE_PATTERN);
Matcher packageMatcher = packagePattern.matcher(content);
if (!packageMatcher.find()) {
- throw new IllegalArgumentException("No package in '" + file + "'\n"+content);
+ // if it doesn't have a package, make sure there's at least a
+ // type declaration otherwise we're probably reading the wrong
+ // kind of file.
+ if (Pattern.compile(TYPE_DECLARATION_PATTERN).matcher(content).find()) {
+ return className;
+ }
+ throw new IllegalArgumentException("Not a .java file: '" + file + "'\n"+content);
}
String packageName = packageMatcher.group(1);
return packageName + "." + className;
import java.util.Properties;
/**
- * Runs a jtreg test.
+ * Runs a test.
*/
public abstract class TestRunner {
new File(DALVIK_RUNNER_HOME + "/java/dalvik/runner/CaliperRunner.java"),
new File(DALVIK_RUNNER_HOME + "/java/dalvik/runner/JUnitRunner.java"),
new File(DALVIK_RUNNER_HOME + "/java/dalvik/runner/JtregRunner.java"),
+ new File(DALVIK_RUNNER_HOME + "/java/dalvik/runner/MainRunner.java"),
new File(DALVIK_RUNNER_HOME + "/java/dalvik/runner/TestRunner.java")));
private final Pattern JAVA_TEST_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");
return;
}
testRun.setTestClasspath(testClasses);
+ prepareUserDir(testRun);
+ }
+
+ /**
+ * Prepares the directory from which the test will be executed. Some tests
+ * expect to read data files from the current working directory; this step
+ * should ensure such files are available.
+ */
+ protected void prepareUserDir(TestRun testRun) {
+ File testUserDir = testUserDir(testRun);
+
+ // if the user dir exists, cp would copy the files to the wrong place
+ if (testUserDir.exists()) {
+ throw new IllegalStateException();
+ }
+
+ testUserDir.getParentFile().mkdirs();
+ new Command("cp", "-r", testRun.getTestDirectory().toString(),
+ testUserDir.toString()).execute();
+ testRun.setUserDir(testUserDir);
}
/**
new Command.Builder().args("rm", "-rf", testClassesDir(testRun).getPath())
.execute();
+ new Command.Builder().args("rm", "-rf", testUserDir(testRun).getPath())
+ .execute();
}
}
return new File(localTemp, testRun.getQualifiedName());
}
+ private File testUserDir(TestRun testRun) {
+ File testTemp = new File(localTemp, "userDir");
+ return new File(testTemp, testRun.getQualifiedName());
+ }
+
/**
* Returns a properties object for the given test description.
*/
throw new IllegalArgumentException();
}
- final Command command = newVmCommandBuilder()
+ final Command command = newVmCommandBuilder(testRun.getUserDir())
.classpath(testRun.getTestClasspath())
.classpath(testRunnerClasses)
.classpath(getRuntimeSupportClasspath())
/**
* Returns a VM for test execution.
*/
- protected VmCommandBuilder newVmCommandBuilder() {
- return new VmCommandBuilder();
- }
+ protected abstract VmCommandBuilder newVmCommandBuilder(File workingDirectory);
/**
* Returns the classpath containing JUnit and the dalvik annotations
public static class VmCommandBuilder {
private File temp;
private Classpath classpath = new Classpath();
+ private File workingDir;
private File userDir;
private Integer debugPort;
private String mainClass;
return this;
}
+ public VmCommandBuilder workingDir(File workingDir) {
+ this.workingDir = workingDir;
+ return this;
+ }
+
public VmCommandBuilder userDir(File userDir) {
this.userDir = userDir;
return this;
builder.args(vmCommand);
builder.args("-classpath", classpath.toString());
builder.args("-Duser.dir=" + userDir);
+ if (workingDir != null) {
+ builder.workingDirectory(workingDir);
+ }
if (temp != null) {
builder.args("-Djava.io.tmpdir=" + temp);