OSDN Git Service

DalvikRunner --tee option to send output to a file or stdout at runtime
authorBrian Carlstrom <bdc@google.com>
Tue, 23 Feb 2010 19:31:46 +0000 (11:31 -0800)
committerBrian Carlstrom <bdc@google.com>
Tue, 23 Feb 2010 21:25:16 +0000 (13:25 -0800)
Added --tee option so we can watch test output while the test is
running, as opposed to waiting until all the output is collected.

As part of this, Command.Builder can now optionally specifiy a
PrintStream via tee (name from tee(1)).

Added ADB.waitForFile to accompany ADB.waitForNonEmptyDirectory

Removed gross bash wait loop hack in Activity Mode by replacing
Mode.buildCommands with Mode.runTestCommand.

Fixed bug that out generated APK package names did not contain a
required "." in all cases.

libcore/tools/runner/java/dalvik/runner/ActivityMode.java
libcore/tools/runner/java/dalvik/runner/Adb.java
libcore/tools/runner/java/dalvik/runner/Command.java
libcore/tools/runner/java/dalvik/runner/DalvikRunner.java
libcore/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
libcore/tools/runner/java/dalvik/runner/JavaVm.java
libcore/tools/runner/java/dalvik/runner/Mode.java
libcore/tools/runner/java/dalvik/runner/Vm.java

index b2b4a3b..163c72a 100644 (file)
@@ -19,10 +19,12 @@ package dalvik.runner;
 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;
@@ -36,11 +38,11 @@ final class ActivityMode extends Mode {
 
     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() {
@@ -148,11 +150,21 @@ final class ActivityMode extends Mode {
         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" +
@@ -202,7 +214,7 @@ final class ActivityMode extends Mode {
 
     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);
     }
 
@@ -211,40 +223,20 @@ final class ActivityMode extends Mode {
         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) {
index 075ca5f..0ab14ec 100644 (file)
@@ -58,10 +58,22 @@ final class Adb {
     }
 
     /**
+     * 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);
@@ -82,8 +94,16 @@ final class Adb {
             } 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;
+                }
             }
         }
     }
index 4319cf9..88ba38e 100644 (file)
@@ -20,6 +20,7 @@ import java.io.BufferedReader;
 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;
@@ -44,6 +45,7 @@ final class Command {
     private final List<String> args;
     private final File workingDirectory;
     private final boolean permitNonZeroExitStatus;
+    private final PrintStream tee;
     private Process process;
 
     Command(String... args) {
@@ -54,12 +56,14 @@ final class Command {
         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() {
@@ -106,6 +110,9 @@ final class Command {
         List<String> outputLines = new ArrayList<String>();
         String outputLine;
         while ((outputLine = in.readLine()) != null) {
+            if (tee != null) {
+                tee.println(outputLine);
+            }
             outputLines.add(outputLine);
         }
 
@@ -167,6 +174,7 @@ final class Command {
         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) {
@@ -200,6 +208,11 @@ final class Command {
             return this;
         }
 
+        public Builder tee(PrintStream printStream) {
+            tee = printStream;
+            return this;
+        }
+
         public Command build() {
             return new Command(this);
         }
index 015fb9a..c78866e 100644 (file)
 
 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;
@@ -72,6 +76,10 @@ public final class DalvikRunner {
         @Option(names = { "--verbose" })
         private boolean verbose;
 
+        @Option(names = { "--tee" })
+        private String teeName;
+        private PrintStream tee;
+
         @Option(names = { "--debug" })
         private Integer debugPort;
 
@@ -116,6 +124,9 @@ public final class DalvikRunner {
             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);
@@ -235,6 +246,19 @@ public final class DalvikRunner {
                 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);
             }
@@ -269,6 +293,7 @@ public final class DalvikRunner {
                     options.debugPort,
                     options.timeoutSeconds,
                     options.sdkJar,
+                    options.tee,
                     localTemp,
                     options.vmArgs,
                     options.cleanBefore,
@@ -279,6 +304,7 @@ public final class DalvikRunner {
                     options.debugPort,
                     options.timeoutSeconds,
                     options.sdkJar,
+                    options.tee,
                     localTemp,
                     options.javaHome,
                     options.vmArgs,
@@ -289,6 +315,7 @@ public final class DalvikRunner {
                     options.debugPort,
                     options.timeoutSeconds,
                     options.sdkJar,
+                    options.tee,
                     localTemp,
                     options.cleanBefore,
                     options.cleanAfter,
index 7bdf482..061e374 100644 (file)
@@ -17,6 +17,7 @@
 package dalvik.runner;
 
 import java.io.File;
+import java.io.PrintStream;
 import java.util.List;
 import java.util.logging.Logger;
 
@@ -26,11 +27,11 @@ 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() {
index 2dfc3e7..38e0386 100644 (file)
@@ -17,6 +17,7 @@
 package dalvik.runner;
 
 import java.io.File;
+import java.io.PrintStream;
 import java.util.List;
 import java.util.Set;
 
@@ -27,11 +28,11 @@ final class JavaVm extends Vm {
 
     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;
     }
 
index c2d6b14..0ad7172 100644 (file)
@@ -20,6 +20,7 @@ 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;
@@ -44,6 +45,7 @@ abstract class Mode {
     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
@@ -72,10 +74,11 @@ abstract class Mode {
             // 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;
     }
 
     /**
@@ -218,20 +221,16 @@ abstract class Mode {
             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()) {
@@ -247,9 +246,10 @@ abstract class Mode {
     }
 
     /**
-     * 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
index 9f96ec5..8ff5858 100644 (file)
@@ -19,6 +19,7 @@ package dalvik.runner;
 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;
@@ -27,6 +28,7 @@ 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;
 
 /**
@@ -39,22 +41,25 @@ public abstract class Vm extends Mode {
     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);
     }
 
     /**
@@ -78,6 +83,7 @@ public abstract class Vm extends Mode {
         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>();
 
@@ -116,6 +122,11 @@ public abstract class Vm extends Mode {
             return this;
         }
 
+        public VmCommandBuilder output(PrintStream output) {
+            this.output = output;
+            return this;
+        }
+
         public VmCommandBuilder vmArgs(String... vmArgs) {
             return vmArgs(Arrays.asList(vmArgs));
         }
@@ -146,6 +157,8 @@ public abstract class Vm extends Mode {
             builder.args(vmArgs);
             builder.args(mainClass);
 
+            builder.tee(output);
+
             return builder.build();
         }
     }