- Improve ScriptRunner command line parsing to actually pass more of the arguments. (And a unit test to go with it).
- Set __name__ inside the scriptfile runner so python scripts expecting it get it.
- Propagate sys.exit error codes so monkeyrunner will actually return those error codes.
- Better handle PyExceptions thrown by the script to actually terminate monkeyrunner.
- Bubble up installPackage and removePackage error codes so callers can tell if they worked or not.

Change-Id: Ia4717b1ad2c9b4cccd607aba00211f2f85dfb412
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
index 5f04d64..f8cecc6 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java
@@ -170,24 +170,26 @@
 
     @MonkeyRunnerExported(doc = "Install the specified apk onto the device.",
             args = { "path" },
-            argDocs = { "The path on the host filesystem to the APK to install." })
-    public void installPackage(PyObject[] args, String[] kws) {
+            argDocs = { "The path on the host filesystem to the APK to install." },
+            returns = "True if install succeeded")
+    public boolean installPackage(PyObject[] args, String[] kws) {
         ArgParser ap = JythonUtils.createArgParser(args, kws);
         Preconditions.checkNotNull(ap);
 
         String path = ap.getString(0);
-        installPackage(path);
+        return installPackage(path);
     }
 
     @MonkeyRunnerExported(doc = "Remove the specified package from the device.",
             args = { "package"},
-            argDocs = { "The name of the package to uninstall"})
-    public void removePackage(PyObject[] args, String[] kws) {
+            argDocs = { "The name of the package to uninstall"},
+            returns = "'True if remove succeeded")
+    public boolean removePackage(PyObject[] args, String[] kws) {
         ArgParser ap = JythonUtils.createArgParser(args, kws);
         Preconditions.checkNotNull(ap);
 
         String packageName = ap.getString(0);
-        removePackage(packageName);
+        return removePackage(packageName);
     }
 
     @MonkeyRunnerExported(doc = "Start the Activity specified by the intent.",
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
index 68577a5..0586045 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
@@ -150,7 +150,9 @@
                 }
 
                 pluginListBuilder.add(plugin);
-            } else if (argument.startsWith("-")) {
+            } else if (argument.startsWith("-") &&
+                // Once we have the scriptfile, the rest of the arguments go to jython.
+                scriptFile == null) {
                 // we have an unrecognized argument.
                 printUsage("Unrecognized argument: " + argument + ".");
                 return null;
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
index 54bb7da..8c32408 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
@@ -78,13 +78,14 @@
         }
     }
 
-    private void run() {
+    private int run() {
         MonkeyRunner.setBackend(backend);
         Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins();
-        ScriptRunner.run(options.getScriptFile().getAbsolutePath(),
-                         options.getArguments(), plugins);
+        int error = ScriptRunner.run(options.getScriptFile().getAbsolutePath(),
+            options.getArguments(), plugins);
         backend.shutdown();
         MonkeyRunner.setBackend(null);
+        return error;
     }
 
     private Predicate<PythonInterpreter> handlePlugin(File f) {
@@ -189,9 +190,9 @@
         }
 
         MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options);
-        runner.run();
+        int error = runner.run();
 
         // This will kill any background threads as well.
-        System.exit(0);
+        System.exit(error);
     }
 }
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
index 7920e50..616ba85 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
@@ -20,6 +20,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.ImmutableMap.Builder;
 
+import org.python.core.Py;
+import org.python.core.PyException;
 import org.python.core.PyObject;
 import org.python.util.InteractiveConsole;
 import org.python.util.PythonInterpreter;
@@ -64,8 +66,9 @@
      * @param scriptfilename the name of the file to run.
      * @param args the arguments passed in (excluding the filename).
      * @param plugins a list of plugins to load.
+     * @return the error code from running the script.
      */
-    public static void run(String scriptfilename, Collection<String> args,
+    public static int run(String scriptfilename, Collection<String> args,
             Map<String, Predicate<PythonInterpreter>> plugins) {
         // Add the current directory of the script to the python.path search path.
         File f = new File(scriptfilename);
@@ -99,7 +102,21 @@
             }
         }
 
-        python.execfile(scriptfilename);
+        // Bind __name__ to __main__ so mains will run
+        python.set("__name__", "__main__");
+
+        try {
+          python.execfile(scriptfilename);
+        } catch (PyException e) {
+          if (Py.SystemExit.equals(e.type)) {
+            // Then recover the error code so we can pass it on
+            return (Integer) e.value.__tojava__(Integer.class);
+          }
+          // Then some other kind of exception was thrown.  Log it and return error;
+          LOG.log(Level.SEVERE, "Script terminated due to an exception", e);
+          return 1;
+        }
+        return 0;
     }
 
     public static void runString(String script) {
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java b/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java
index c5f0d67..645360e 100644
--- a/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java
@@ -35,7 +35,8 @@
 
     public static void main(String args[]) {
         TestRunner tr = new TestRunner();
-        TestResult result = tr.doRun(AllTests.suite(ImageUtilsTest.class, JythonUtilsTest.class));
+        TestResult result = tr.doRun(AllTests.suite(ImageUtilsTest.class, JythonUtilsTest.class,
+            MonkeyRunnerOptionsTest.class));
         if (result.wasSuccessful()) {
             System.exit(0);
         } else {
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/MonkeyRunnerOptionsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/MonkeyRunnerOptionsTest.java
new file mode 100644
index 0000000..0852c0b
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/MonkeyRunnerOptionsTest.java
@@ -0,0 +1,56 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.android.monkeyrunner;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.util.Iterator;
+
+/**
+ * Unit Tests to test command line argument parsing.
+ */
+public class MonkeyRunnerOptionsTest extends TestCase {
+  // We need to use a file that actually exists
+  private static final String FILENAME = "/etc/passwd";
+
+  public void testSimpleArgs() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { FILENAME });
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+  }
+
+  public void testParsingArgsBeforeScriptName() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { "-be", "stub", FILENAME});
+    assertEquals("stub", options.getBackendName());
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+  }
+
+  public void testParsingScriptArgument() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { FILENAME, "arg1", "arg2" });
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+    Iterator<String> i = options.getArguments().iterator();
+    assertEquals("arg1", i.next());
+    assertEquals("arg2", i.next());
+  }
+
+  public void testParsingScriptArgumentWithDashes() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { FILENAME, "--arg1" });
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+    assertEquals("--arg1", options.getArguments().iterator().next());
+  }
+
+  public void testMixedArgs() {
+    MonkeyRunnerOptions options =
+      MonkeyRunnerOptions.processOptions(new String[] { "-be", "stub", FILENAME,
+          "arg1", "--debug=True"});
+    assertEquals("stub", options.getBackendName());
+    assertEquals(options.getScriptFile(), new File(FILENAME));
+    Iterator<String> i = options.getArguments().iterator();
+    assertEquals("arg1", i.next());
+    assertEquals("--debug=True", i.next());
+  }
+}