blob: 0ad7172272931db0580b0e4133b75f02f42efa04 [file] [log] [blame]
/*
* Copyright (C) 2010 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.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* A Mode for running tests. Examples including running in a virtual
* machine either on the host or a device or within a specific context
* such as within an Activity.
*/
abstract class Mode {
private static final Pattern JAVA_TEST_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");
private static final Logger logger = Logger.getLogger(Mode.class.getName());
protected final Environment environment;
protected final long timeoutSeconds;
protected final File sdkJar;
protected final PrintStream tee;
/**
* Set of Java files needed to built to tun the currently selected
* set of tests. We build a subset rather than all the files all
* the time to reduce dex packaging costs in the activity mode
* case.
*/
protected final Set<File> testRunnerJava = new HashSet<File>();
/**
* Classpath of testRunner on the host side including any
* supporting libraries for testRunnerJava. Useful for compiling
* testRunnerJava as well as executing it on the host. Execution
* on the device requires further packaging typically done by
* postCompileTestRunner.
*/
protected final Classpath testRunnerClasspath = new Classpath();
// TODO: this should be an immutable collection.
protected final Classpath testClasspath = Classpath.of(
new File("dalvik/libcore/tools/runner/lib/jsr305.jar"),
new File("dalvik/libcore/tools/runner/lib/guava.jar"),
new File("dalvik/libcore/tools/runner/lib/caliper.jar"),
// TODO: we should be able to work with a shipping SDK, not depend on out/...
// dalvik/libcore/**/test/ for junit
// TODO: jar up just the junit classes and drop the jar in our lib/ directory.
new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar").getAbsoluteFile());
Mode(Environment environment, long timeoutSeconds, File sdkJar, PrintStream tee) {
this.environment = environment;
this.timeoutSeconds = timeoutSeconds;
this.sdkJar = sdkJar;
this.tee = tee;
}
/**
* Initializes the temporary directories and test harness necessary to run
* tests.
*/
protected void prepare(Set<File> testRunnerJava, Classpath testRunnerClasspath) {
this.testRunnerJava.add(new File(DalvikRunner.HOME_JAVA, "dalvik/runner/TestRunner.java"));
this.testRunnerJava.addAll(dalvikAnnotationSourceFiles());
this.testRunnerJava.addAll(testRunnerJava);
this.testRunnerClasspath.addAll(testRunnerClasspath);
environment.prepare();
compileTestRunner();
}
private List<File> dalvikAnnotationSourceFiles() {
// Hopefully one day we'll strip the dalvik annotations out, but until then we need to make
// them available to javac(1).
File sourceDir = new File("dalvik/libcore/dalvik/src/main/java/dalvik/annotation");
File[] javaSourceFiles = sourceDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String filename) {
return filename.endsWith(".java");
}
});
return Arrays.asList(javaSourceFiles);
}
private void compileTestRunner() {
logger.fine("build testrunner");
Classpath classpath = new Classpath();
classpath.addAll(testClasspath);
classpath.addAll(testRunnerClasspath);
File base = environment.testRunnerClassesDir();
new Mkdir().mkdirs(base);
new Javac()
.bootClasspath(sdkJar)
.classpath(classpath)
.sourcepath(DalvikRunner.HOME_JAVA)
.destination(base)
.compile(testRunnerJava);
postCompileTestRunner();
}
/**
* Hook method called after TestRunner compilation.
*/
abstract protected void postCompileTestRunner();
/**
* 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) {
logger.fine("build " + testRun.getQualifiedName());
boolean testCompiled;
try {
testCompiled = compileTest(testRun);
if (!testCompiled) {
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.setTestCompiled(testCompiled);
environment.prepareUserDir(testRun);
}
/**
* 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 boolean compileTest(TestRun testRun) throws IOException {
if (!JAVA_TEST_PATTERN.matcher(testRun.getTestJava().toString()).find()) {
return false;
}
String qualifiedName = testRun.getQualifiedName();
File testClassesDir = environment.testClassesDir(testRun);
new Mkdir().mkdirs(testClassesDir);
FileOutputStream propertiesOut = new FileOutputStream(
new File(testClassesDir, TestProperties.FILE));
Properties properties = new Properties();
fillInProperties(properties, testRun);
properties.store(propertiesOut, "generated by " + Mode.class.getName());
propertiesOut.close();
Classpath classpath = new Classpath();
classpath.addAll(testClasspath);
classpath.addAll(testRun.getRunnerClasspath());
Set<File> sourceFiles = new HashSet<File>();
sourceFiles.add(testRun.getTestJava());
sourceFiles.addAll(dalvikAnnotationSourceFiles());
// compile the test case
new Javac()
.bootClasspath(sdkJar)
.classpath(classpath)
.sourcepath(testRun.getTestDirectory())
.destination(testClassesDir)
.compile(sourceFiles);
postCompileTest(testRun);
return true;
}
/**
* Hook method called after test compilation.
*
* @param testRun The test being compiled
*/
abstract protected void postCompileTest(TestRun testRun);
/**
* Fill in properties for running in this mode
*/
protected void fillInProperties(Properties properties, TestRun testRun) {
properties.setProperty(TestProperties.TEST_CLASS, testRun.getTestClass());
properties.setProperty(TestProperties.QUALIFIED_NAME, testRun.getQualifiedName());
properties.setProperty(TestProperties.RUNNER_CLASS, testRun.getRunnerClass().getName());
}
/**
* Runs the test, and updates its test result.
*/
void runTest(TestRun testRun) {
if (!testRun.isRunnable()) {
throw new IllegalArgumentException();
}
List<String> output;
try {
output = runTestCommand(testRun);
} catch (TimeoutException e) {
testRun.setResult(Result.EXEC_TIMEOUT,
Collections.singletonList("Exceeded timeout! (" + timeoutSeconds + "s)"));
return;
} catch (Exception e) {
testRun.setResult(Result.ERROR, e);
return;
}
// we only look at the output of the last command
if (output.isEmpty()) {
testRun.setResult(Result.ERROR,
Collections.singletonList("No output returned!"));
return;
}
Result result = TestProperties.RESULT_SUCCESS.equals(output.get(output.size() - 1))
? Result.SUCCESS
: Result.EXEC_FAILED;
testRun.setResult(result, output.subList(0, output.size() - 1));
}
/**
* Run the actual test to gather output
*/
protected abstract List<String> runTestCommand(TestRun testRun)
throws TimeoutException;
/**
* Deletes files and releases any resources required for the execution of
* the given test.
*/
void cleanup(TestRun testRun) {
environment.cleanup(testRun);
}
/**
* Cleans up after all test runs have completed.
*/
void shutdown() {
environment.shutdown();
}
}