import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
private static final String TEST_ACTIVITY_CLASS = "dalvik.runner.TestActivity";
- ActivityMode(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp,
+ ActivityMode(Integer debugPort, long timeoutSeconds, File sdkJar, PrintStream tee, File localTemp,
boolean cleanBefore, boolean cleanAfter, File deviceRunnerDir) {
super(new EnvironmentDevice(cleanBefore, cleanAfter,
debugPort, localTemp, deviceRunnerDir),
- timeoutSeconds, sdkJar);
+ timeoutSeconds, sdkJar, tee);
}
private EnvironmentDevice getEnvironmentDevice() {
return dex;
}
+ /**
+ * According to android.content.pm.PackageParser, package name
+ * "must have at least one '.' separator" Since the qualified name
+ * may not contain a dot, we prefix containing one to ensure we
+ * are compliant.
+ */
+ private static String packageName(TestRun testRun) {
+ return "DalvikRunner." + testRun.getQualifiedName();
+ }
+
private File createApk (TestRun testRun, File dex) {
String androidManifest =
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +
- " package=\"" + testRun.getQualifiedName() + "\">\n" +
+ " package=\"" + packageName(testRun) + "\">\n" +
" <uses-permission android:name=\"android.permission.INTERNET\" />\n" +
" <application>\n" +
" <activity android:name=\"" + TEST_ACTIVITY_CLASS + "\">\n" +
private void installApk(TestRun testRun, File apkSigned) {
// install the local apk ona the device
- getEnvironmentDevice().adb.uninstall(testRun.getQualifiedName());
+ getEnvironmentDevice().adb.uninstall(packageName(testRun));
getEnvironmentDevice().adb.install(apkSigned);
}
properties.setProperty(TestProperties.DEVICE_RUNNER_DIR, getEnvironmentDevice().runnerDir.getPath());
}
- @Override protected List<Command> buildCommands(TestRun testRun) {
- List<Command> commands = new ArrayList<Command>();
- commands.add(new Command.Builder()
- .args("adb")
- .args("shell")
- .args("am")
- .args("start")
- .args("-a")
- .args("android.intent.action.MAIN")
- .args("-n")
- .args(testRun.getQualifiedName() + "/" + TEST_ACTIVITY_CLASS).build());
+ @Override protected List<String> runTestCommand(TestRun testRun)
+ throws TimeoutException {
+ new Command(
+ "adb", "shell", "am", "start",
+ "-a","android.intent.action.MAIN",
+ "-n", (packageName(testRun) + "/" + TEST_ACTIVITY_CLASS)).executeWithTimeout(timeoutSeconds);
File resultDir = new File(getEnvironmentDevice().runnerDir, testRun.getQualifiedName());
File resultFile = new File(resultDir, TestProperties.RESULT_FILE);
- /*
- * The follow bash script waits for the result file to
- * exist. It polls once a second to see if it is there with
- * "adb shell ls". The "tr" is to remove the carriage return
- * and newline from the adb output. When it does exist, we
- * "adb shell cat" it so we can see the SUCCESS/FAILURE
- * results that are expected by Mode.runTest.
- */
- // TODO: move loop to Java
- commands.add(new Command.Builder()
- .args("bash")
- .args("-c")
- .args(
- "while [ ! \"`adb shell ls " + resultFile + " | tr -d '\\r\\n'`\" = " +
- " \"" + resultFile + "\" ] ; do " +
- " sleep 1; " +
- "done; " +
- "adb shell cat " + resultFile).build());
-
- return commands;
+ getEnvironmentDevice().adb.waitForFile(resultFile, timeoutSeconds);
+ return new Command.Builder()
+ .args("adb", "shell", "cat", resultFile.getPath())
+ .tee(tee)
+ .build().executeWithTimeout(timeoutSeconds);
}
@Override void cleanup(TestRun testRun) {
}
/**
+ * Loop until we see a file on the device. For example, wait
+ * result.txt appears.
+ */
+ public void waitForFile(File file, long timeoutSeconds) {
+ waitFor(true, file, timeoutSeconds);
+ }
+
+ /**
* Loop until we see a non-empty directory on the device. For
* example, wait until /sdcard is mounted.
*/
- public void waitForNonEmptyDirectory(File path, int timeoutSeconds) {
+ public void waitForNonEmptyDirectory(File path, long timeoutSeconds) {
+ waitFor(false, path, timeoutSeconds);
+ }
+
+ private void waitFor(boolean file, File path, long timeoutSeconds) {
final int millisPerSecond = 1000;
final long start = System.currentTimeMillis();
final long deadline = start + (millisPerSecond * timeoutSeconds);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
- if (!output.isEmpty()) {
- return;
+ if (file) {
+ // for files, we expect one line of output that matches the filename
+ if (output.size() == 1 && output.get(0).equals(path.getPath())) {
+ return;
+ }
+ } else {
+ // for a non empty directory, we just want any output
+ if (!output.isEmpty()) {
+ return;
+ }
}
}
}
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
private final List<String> args;
private final File workingDirectory;
private final boolean permitNonZeroExitStatus;
+ private final PrintStream tee;
private Process process;
Command(String... args) {
this.args = new ArrayList<String>(args);
this.workingDirectory = null;
this.permitNonZeroExitStatus = false;
+ this.tee = null;
}
private Command(Builder builder) {
this.args = new ArrayList<String>(builder.args);
this.workingDirectory = builder.workingDirectory;
this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
+ this.tee = builder.tee;
}
public List<String> getArgs() {
List<String> outputLines = new ArrayList<String>();
String outputLine;
while ((outputLine = in.readLine()) != null) {
+ if (tee != null) {
+ tee.println(outputLine);
+ }
outputLines.add(outputLine);
}
private final List<String> args = new ArrayList<String>();
private File workingDirectory;
private boolean permitNonZeroExitStatus = false;
+ private PrintStream tee = null;
public Builder args(Object... objects) {
for (Object object : objects) {
return this;
}
+ public Builder tee(PrintStream printStream) {
+ tee = printStream;
+ return this;
+ }
+
public Command build() {
return new Command(this);
}
package dalvik.runner;
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
@Option(names = { "--verbose" })
private boolean verbose;
+ @Option(names = { "--tee" })
+ private String teeName;
+ private PrintStream tee;
+
@Option(names = { "--debug" })
private Integer debugPort;
System.out.println(" --clean: synonym for --clean-before and --clean-after (default).");
System.out.println(" Disable with --no-clean if you want no files removed.");
System.out.println();
+ System.out.println(" --tee <file>: emit test output to file during execution.");
+ System.out.println(" Specify '-' for stdout.");
+ 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);
testFiles.add(new File(testFilename));
}
+ if (teeName != null) {
+ if (teeName.equals("-")) {
+ tee = System.out;
+ } else {
+ try {
+ tee = new PrintStream(new BufferedOutputStream(new FileOutputStream(teeName)));
+ } catch (FileNotFoundException e) {
+ System.out.println("Could not open file teeName: " + e);
+ return false;
+ }
+ }
+ }
+
if (verbose) {
Logger.getLogger("dalvik.runner").setLevel(Level.FINE);
}
options.debugPort,
options.timeoutSeconds,
options.sdkJar,
+ options.tee,
localTemp,
options.vmArgs,
options.cleanBefore,
options.debugPort,
options.timeoutSeconds,
options.sdkJar,
+ options.tee,
localTemp,
options.javaHome,
options.vmArgs,
options.debugPort,
options.timeoutSeconds,
options.sdkJar,
+ options.tee,
localTemp,
options.cleanBefore,
options.cleanAfter,
package dalvik.runner;
import java.io.File;
+import java.io.PrintStream;
import java.util.List;
import java.util.logging.Logger;
final class DeviceDalvikVm extends Vm {
private static final Logger logger = Logger.getLogger(DeviceDalvikVm.class.getName());
- DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar,
+ DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar, PrintStream tee,
File localTemp, List<String> additionalVmArgs,
boolean cleanBefore, boolean cleanAfter, File runnerDir) {
super(new EnvironmentDevice(cleanBefore, cleanAfter, debugPort, localTemp, runnerDir),
- timeoutSeconds, sdkJar, additionalVmArgs);
+ timeoutSeconds, sdkJar, tee, additionalVmArgs);
}
private EnvironmentDevice getEnvironmentDevice() {
package dalvik.runner;
import java.io.File;
+import java.io.PrintStream;
import java.util.List;
import java.util.Set;
private final File javaHome;
- JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp,
- File javaHome, List<String> additionalVmArgs,
+ JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar, PrintStream tee,
+ File localTemp, File javaHome, List<String> additionalVmArgs,
boolean cleanBefore, boolean cleanAfter) {
super(new EnvironmentHost(cleanBefore, cleanAfter, debugPort, localTemp),
- timeoutSeconds, sdkJar, additionalVmArgs);
+ timeoutSeconds, sdkJar, tee, additionalVmArgs);
this.javaHome = javaHome;
}
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;
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
// 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) {
+ Mode(Environment environment, long timeoutSeconds, File sdkJar, PrintStream tee) {
this.environment = environment;
this.timeoutSeconds = timeoutSeconds;
this.sdkJar = sdkJar;
+ this.tee = tee;
}
/**
throw new IllegalArgumentException();
}
- final List<Command> commands = buildCommands(testRun);
-
- List<String> output = null;
- for (final Command command : commands) {
- try {
- output = command.executeWithTimeout(timeoutSeconds);
- } catch (TimeoutException e) {
- testRun.setResult(Result.EXEC_TIMEOUT,
- Collections.singletonList("Exceeded timeout! (" + timeoutSeconds + "s)"));
- return;
- } catch (Exception e) {
- testRun.setResult(Result.ERROR, e);
- return;
- }
+ 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()) {
}
/**
- * Returns commands for test execution.
+ * Run the actual test to gather output
*/
- protected abstract List<Command> buildCommands(TestRun testRun);
+ protected abstract List<String> runTestCommand(TestRun testRun)
+ throws TimeoutException;
/**
* Deletes files and releases any resources required for the execution of
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
/**
protected final List<String> additionalVmArgs;
Vm(Environment environment, long timeoutSeconds, File sdkJar,
- List<String> additionalVmArgs) {
- super(environment, timeoutSeconds, sdkJar);
+ PrintStream tee, List<String> additionalVmArgs) {
+ super(environment, timeoutSeconds, sdkJar, tee);
this.additionalVmArgs = additionalVmArgs;
}
/**
* Returns a VM for test execution.
*/
- @Override protected List<Command> buildCommands(TestRun testRun) {
- return Collections.singletonList(newVmCommandBuilder(testRun.getUserDir())
+ @Override protected List<String> runTestCommand(TestRun testRun)
+ throws TimeoutException {
+ Command command = newVmCommandBuilder(testRun.getUserDir())
.classpath(getRuntimeSupportClasspath(testRun))
.userDir(testRun.getUserDir())
.debugPort(environment.debugPort)
.vmArgs(additionalVmArgs)
.mainClass(TestRunner.class.getName())
- .build());
+ .output(tee)
+ .build();
+ return command.executeWithTimeout(timeoutSeconds);
}
/**
private File userDir;
private Integer debugPort;
private String mainClass;
+ private PrintStream output;
private List<String> vmCommand = Collections.singletonList("java");
private List<String> vmArgs = new ArrayList<String>();
return this;
}
+ public VmCommandBuilder output(PrintStream output) {
+ this.output = output;
+ return this;
+ }
+
public VmCommandBuilder vmArgs(String... vmArgs) {
return vmArgs(Arrays.asList(vmArgs));
}
builder.args(vmArgs);
builder.args(mainClass);
+ builder.tee(output);
+
return builder.build();
}
}