Adding timeouts to jtreg runner.

Also fixing some problems with the Android.mk which was
pointing at an old version of the Run class.
diff --git a/libcore/tools/dalvik_jtreg/Android.mk b/libcore/tools/dalvik_jtreg/Android.mk
index 8ca5015..f32312a 100644
--- a/libcore/tools/dalvik_jtreg/Android.mk
+++ b/libcore/tools/dalvik_jtreg/Android.mk
@@ -7,12 +7,12 @@
         java/dalvik/jtreg/Command.java \
         java/dalvik/jtreg/CommandFailedException.java \
         java/dalvik/jtreg/Dx.java \
-        java/dalvik/jtreg/Dalvikvm.java \
         java/dalvik/jtreg/ExpectedResult.java \
         java/dalvik/jtreg/Javac.java \
         java/dalvik/jtreg/JtregRunner.java \
-        java/dalvik/jtreg/Run.java \
+        java/dalvik/jtreg/Result.java \
         java/dalvik/jtreg/Strings.java \
+        java/dalvik/jtreg/TestRun.java \
         java/dalvik/jtreg/TestDescriptions.java \
         java/dalvik/jtreg/TestRunner.java \
         java/dalvik/jtreg/TestToDex.java \
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java
index fb644bf..9682a18 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Command.java
@@ -23,6 +23,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.Callable;
 
 /**
  * An out of process executable.
@@ -31,6 +32,7 @@
 
     private final List<String> args;
     private final boolean permitNonZeroExitStatus;
+    private Process process;
 
     Command(String... args) {
         this(Arrays.asList(args));
@@ -46,10 +48,69 @@
         this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
     }
 
-    static String path(Object[] objects) {
+    static String path(Object... objects) {
         return Strings.join(objects, ":");
     }
 
+    public synchronized void start() throws IOException {
+        if (isStarted()) {
+            throw new IllegalStateException("Already started!");
+        }
+
+        process = new ProcessBuilder()
+                .command(args)
+                .redirectErrorStream(true)
+                .start();
+    }
+
+    public boolean isStarted() {
+        return process != null;
+    }
+
+    public Process getProcess() {
+        if (!isStarted()) {
+            throw new IllegalStateException("Not started!");
+        }
+
+        return process;
+    }
+
+    public synchronized List<String> gatherOutput()
+            throws IOException, InterruptedException {
+        if (!isStarted()) {
+            throw new IllegalStateException("Not started!");
+        }
+
+        BufferedReader in = new BufferedReader(
+                new InputStreamReader(process.getInputStream()));
+        List<String> outputLines = new ArrayList<String>();
+        String outputLine;
+        while ((outputLine = in.readLine()) != null) {
+            outputLines.add(outputLine);
+        }
+
+        if (process.waitFor() != 0 && !permitNonZeroExitStatus) {
+            StringBuilder message = new StringBuilder();
+            for (String line : outputLines) {
+                message.append("\n").append(line);
+            }
+            throw new CommandFailedException(args, outputLines);
+        }
+
+        return outputLines;
+    }
+
+    public synchronized List<String> execute() {
+        try {
+            start();
+            return gatherOutput();
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to execute process: " + args, e);
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while executing process: " + args, e);
+        }
+    }
+
     static class Builder {
         private final List<String> args = new ArrayList<String>();
         private boolean permitNonZeroExitStatus = false;
@@ -76,35 +137,4 @@
             return build().execute();
         }
     }
-
-    public List<String> execute() {
-        try {
-            Process process = new ProcessBuilder()
-                    .command(args)
-                    .redirectErrorStream(true)
-                    .start();
-
-            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
-            List<String> outputLines = new ArrayList<String>();
-            String outputLine;
-            while ((outputLine = in.readLine()) != null) {
-                outputLines.add(outputLine);
-            }
-
-            if (process.waitFor() != 0 && !permitNonZeroExitStatus) {
-                StringBuilder message = new StringBuilder();
-                for (String line : outputLines) {
-                    message.append("\n").append(line);
-                }
-                throw new CommandFailedException(args, outputLines);
-            }
-
-            return outputLines;
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to execute process: " + args, e);
-        } catch (InterruptedException e) {
-            throw new RuntimeException("Interrupted while executing process: " + args, e);
-        }
-    }
-
 }
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Dalvikvm.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Dalvikvm.java
deleted file mode 100644
index 9137703..0000000
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Dalvikvm.java
+++ /dev/null
@@ -1,49 +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 java.io.File;
-import java.util.List;
-
-/**
- * A dalvikvm command.
- */
-final class Dalvikvm {
-
-    private final Command.Builder builder = new Command.Builder();
-
-    public Dalvikvm() {
-        builder.args("adb", "shell", "dalvikvm");
-    }
-
-    Dalvikvm classpath(File... files) {
-        builder.args("-classpath");
-        builder.args(Command.path(files));
-        return this;
-    }
-
-    List<String> exec(String classname, String... args) {
-        builder.args(classname);
-        builder.args(args);
-        return builder.execute();
-    }
-
-    public Dalvikvm args(String... args) {
-        builder.args(args);
-        return this;
-    }
-}
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Javac.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Javac.java
index c168d5d..0808200 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Javac.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Javac.java
@@ -31,17 +31,17 @@
     }
 
     public Javac bootClasspath(File... path) {
-        builder.args("-bootclasspath", Command.path(path));
+        builder.args("-bootclasspath", Command.path((Object[]) path));
         return this;
     }
 
     public Javac classpath(File... path) {
-        builder.args("-classpath", Command.path(path));
+        builder.args("-classpath", Command.path((Object[]) path));
         return this;
     }
 
     public Javac sourcepath(File... path) {
-        builder.args("-sourcepath", Command.path(path));
+        builder.args("-sourcepath", Command.path((Object[]) path));
         return this;
     }
 
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java
index 0d00b7a..0ef24b2 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/JtregRunner.java
@@ -27,8 +27,11 @@
 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;
@@ -49,9 +52,11 @@
     private final Adb adb = new Adb();
     private final File directoryToScan;
     private final TestToDex testToDex;
+    private final ExecutorService outputReaders = Executors.newFixedThreadPool(1);
 
     private Integer debugPort;
     private Set<File> expectationDirs = new LinkedHashSet<File>();
+    private long timeoutSeconds = 10 * 60; // default is ten minutes
 
     private File deviceTestRunner;
 
@@ -146,21 +151,21 @@
         try {
             dex = testToDex.dexify(testDescription);
             if (dex == null) {
-                testRun.initResult(Result.UNSUPPORTED, Collections.<String>emptyList());
+                testRun.setResult(Result.UNSUPPORTED, Collections.<String>emptyList());
                 return;
             }
         } catch (CommandFailedException e) {
-            testRun.initResult(Result.COMPILE_FAILED, e.getOutputLines());
+            testRun.setResult(Result.COMPILE_FAILED, e.getOutputLines());
             return;
         } catch (IOException e) {
-            testRun.initResult(Result.ERROR, e);
+            testRun.setResult(Result.ERROR, e);
             return;
         }
 
         logger.fine("installing " + testRun.getQualifiedName());
         adb.push(testDescription.getDir(), base);
         adb.push(dex, deviceTemp);
-        testRun.initInstalledFiles(base, new File(deviceTemp, dex.getName()));
+        testRun.setInstalledFiles(base, new File(deviceTemp, dex.getName()));
     }
 
     /**
@@ -171,25 +176,47 @@
             throw new IllegalArgumentException();
         }
 
-        logger.fine("running " + testRun.getQualifiedName());
-        Dalvikvm vm = new Dalvikvm()
-                .classpath(testRun.getDeviceDex(), deviceTestRunner)
-                .args("-Duser.dir=" + testRun.getBase());
+        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());
         if (debugPort != null) {
-            vm.args("-Xrunjdwp:transport=dt_socket,address="
+            builder.args("-Xrunjdwp:transport=dt_socket,address="
                     + debugPort + ",server=y,suspend=y");
         }
-        List<String> output = vm.exec("dalvik.jtreg.TestRunner");
+        builder.args("dalvik.jtreg.TestRunner");
+        final Command command = builder.build();
 
-        if (output.isEmpty()) {
-            testRun.initResult(Result.ERROR,
-                    Collections.singletonList("No output returned!"));
+        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
+            }
         }
-
-        Result result = "SUCCESS".equals(output.get(output.size() - 1))
-                ? Result.SUCCESS
-                : Result.EXEC_FAILED;
-        testRun.initResult(result, output.subList(0, output.size() - 1));
     }
 
     private void printResult(TestRun testRun) {
@@ -249,6 +276,9 @@
             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("  --timeoutSeconds <seconds>: maximum execution time of each test");
+            System.out.println("      before the runner aborts it.");
+            System.out.println();
             System.out.println("  --verbose: turn on verbose output");
             System.out.println();
             return;
@@ -272,6 +302,9 @@
             if ("--debug".equals(args[i])) {
                 jtregRunner.debugPort = Integer.valueOf(args[++i]);
 
+            } else if ("--timeoutSeconds".equals(args[i])) {
+                jtregRunner.timeoutSeconds = Long.valueOf(args[++i]);
+
             } else if ("--verbose".equals(args[i])) {
                 Logger.getLogger("dalvik.jtreg").setLevel(Level.FINE);
 
@@ -298,6 +331,7 @@
                 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/Result.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Result.java
index 1189e76..b612407 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Result.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/Result.java
@@ -28,6 +28,7 @@
 
     COMPILE_FAILED,
     EXEC_FAILED,
+    EXEC_TIMEOUT,
     ERROR,
     SUCCESS
 }
diff --git a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java
index aa85528..feb919a 100644
--- a/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java
+++ b/libcore/tools/dalvik_jtreg/java/dalvik/jtreg/TestRun.java
@@ -59,7 +59,7 @@
      * Initializes the on-device base directory from which the test program
      * shall be executed, and the dex file containing that program.
      */
-    public void initInstalledFiles(File base, File deviceDex) {
+    public void setInstalledFiles(File base, File deviceDex) {
         if (this.base != null) {
             throw new IllegalStateException();
         }
@@ -90,11 +90,11 @@
         return deviceDex;
     }
 
-    public void initResult(Result result, Exception e) {
-        initResult(result, throwableToLines(e));
+    public void setResult(Result result, Throwable e) {
+        setResult(result, throwableToLines(e));
     }
 
-    public void initResult(Result result, List<String> outputLines) {
+    public void setResult(Result result, List<String> outputLines) {
         if (this.result != null) {
             throw new IllegalStateException();
         }