Update ScriptRunner to correctly pass in the name of the sript being started (so jython's sys.argv will work).
Add in JythinUtils Unit Tests to validate JythonUtils correctness.

Change-Id: I41ea78b3efbb8649840b46c279f3e91f12b960ac
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java b/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
index 6fe46a2..cfd4c41 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java
@@ -23,6 +23,7 @@
 import com.android.monkeyrunner.doc.MonkeyRunnerExported;
 
 import org.python.core.ArgParser;
+import org.python.core.Py;
 import org.python.core.PyDictionary;
 import org.python.core.PyFloat;
 import org.python.core.PyInteger;
@@ -52,7 +53,8 @@
         Builder<Class<? extends PyObject>, Class<?>> builder = ImmutableMap.builder();
 
         builder.put(PyString.class, String.class);
-        builder.put(PyFloat.class, Float.class);
+        // What python calls float, most people call double
+        builder.put(PyFloat.class, Double.class);
         builder.put(PyInteger.class, Integer.class);
 
         PYOBJECT_TO_JAVA_OBJECT_MAP = builder.build();
@@ -85,7 +87,7 @@
         Method m;
 
         try {
-            m = MonkeyRunner.class.getMethod(methodName, PyObject[].class, String[].class);
+            m = clz.getMethod(methodName, PyObject[].class, String[].class);
         } catch (SecurityException e) {
             LOG.log(Level.SEVERE, "Got exception: ", e);
             return null;
@@ -107,38 +109,47 @@
      * @return the double value
      */
     public static double getFloat(ArgParser ap, int position) {
-        // cast is safe as getPyObjectbyType ensures it
-        PyFloat object = (PyFloat) ap.getPyObjectByType(position, PyFloat.TYPE);
-        return object.asDouble();
+        PyObject arg = ap.getPyObject(position);
+
+        if (Py.isInstance(arg, PyFloat.TYPE)) {
+            return ((PyFloat) arg).asDouble();
+        }
+        if (Py.isInstance(arg, PyInteger.TYPE)) {
+            return ((PyInteger) arg).asDouble();
+        }
+        throw Py.TypeError("Unable to parse argument: " + position);
     }
 
     /**
      * Get a list of arguments from an ArgParser.
      *
-     * @param <T> the type of list items to return
      * @param ap the ArgParser
      * @param position the position in the parser to get the argument from
-     * @param clz the type of items to return
      * @return a list of those items
      */
     @SuppressWarnings("unchecked")
-    public static <T> List<T> getList(ArgParser ap, int position, Class<?> clz) {
-        List<T> ret = Lists.newArrayList();
+    public static List<Object> getList(ArgParser ap, int position) {
+        List<Object> ret = Lists.newArrayList();
         // cast is safe as getPyObjectbyType ensures it
         PyList array = (PyList) ap.getPyObjectByType(position, PyList.TYPE);
         for (int x = 0; x < array.__len__(); x++) {
-            T item = (T) array.__getitem__(x).__tojava__(clz);
-            ret.add(item);
+            PyObject item = array.__getitem__(x);
+
+            Class<?> javaClass = PYOBJECT_TO_JAVA_OBJECT_MAP.get(item.getClass());
+            if (javaClass != null) {
+                ret.add(item.__tojava__(javaClass));
+            }
         }
         return ret;
     }
 
     /**
-     * Get a dictionary from an ArgParser.
+     * Get a dictionary from an ArgParser.  For ease of use, key types are always coerced to
+     * strings.  If key type cannot be coeraced to string, an exception is raised.
      *
      * @param ap the ArgParser to work with
      * @param position the position in the parser to get.
-     * @return a Map mapping the String key to their underlying type.
+     * @return a Map mapping the String key to the value
      */
     public static Map<String, Object> getMap(ArgParser ap, int position) {
         Map<String, Object> ret = Maps.newHashMap();
@@ -148,7 +159,8 @@
         for (int x = 0; x < items.__len__(); x++) {
             // It's a list of tuples
             PyTuple item = (PyTuple) items.__getitem__(x);
-            String key = (String) item.__getitem__(0).__tojava__(String.class);
+            // We call str(key) on the key to get the string and then convert it to the java string.
+            String key = (String) item.__getitem__(0).__str__().__tojava__(String.class);
             PyObject value = item.__getitem__(1);
 
             // Look up the conversion type and convert the value
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
index bbed437..2c145e6 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
@@ -15,11 +15,16 @@
  */
 package com.android.monkeyrunner;
 
+import com.google.common.collect.Lists;
+
 import org.python.core.PyObject;
 import org.python.util.InteractiveConsole;
 import org.python.util.PythonInterpreter;
 
+import java.io.File;
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
 import java.util.Properties;
 
 
@@ -49,7 +54,11 @@
      */
     public static void run(String scriptfilename) {
         try {
-            initPython();
+            // Add the current directory of the script to the python.path search path.
+            File f = new File(scriptfilename);
+            initPython(Lists.newArrayList(f.getParent()),
+                    new String[] { f.getCanonicalPath() });
+
             PythonInterpreter python = new PythonInterpreter();
 
             python.execfile(scriptfilename);
@@ -58,15 +67,35 @@
         }
     }
 
+    public static void runString(String script) {
+        initPython();
+        PythonInterpreter python = new PythonInterpreter();
+        python.exec(script);
+    }
 
-    /** Initialize the python interpreter. */
     private static void initPython() {
+        List<String> arg = Collections.emptyList();
+        initPython(arg, new String[] {""});
+    }
+
+    private static void initPython(List<String> pythonPath,
+            String[] argv) {
         Properties props = new Properties();
+
+        // Build up the python.path
+        StringBuilder sb = new StringBuilder();
+        sb.append(System.getProperty("java.class.path"));
+        for (String p : pythonPath) {
+            sb.append(":").append(p);
+        }
+        props.setProperty("python.path", sb.toString());
+
+        /** Initialize the python interpreter. */
         // Default is 'message' which displays sys-package-mgr bloat
         // Choose one of error,warning,message,comment,debug
         props.setProperty("python.verbose", "error");
-        props.setProperty("python.path", System.getProperty("java.class.path"));
-        PythonInterpreter.initialize(System.getProperties(), props, new String[] {""});
+
+        PythonInterpreter.initialize(System.getProperties(), props, argv);
     }
 
     /**
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
new file mode 100644
index 0000000..bbe1583
--- /dev/null
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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 com.android.monkeyrunner;
+
+import com.google.common.base.Preconditions;
+
+import com.android.monkeyrunner.doc.MonkeyRunnerExported;
+
+import junit.framework.TestCase;
+
+import org.python.core.ArgParser;
+import org.python.core.PyException;
+import org.python.core.PyObject;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for the JythonUtils class.
+ */
+public class JythonUtilsTest extends TestCase {
+    private static final String PACKAGE_NAME = JythonUtilsTest.class.getPackage().getName();
+    private static final String CLASS_NAME = JythonUtilsTest.class.getSimpleName();
+
+    private static boolean called = false;
+    private static double floatValue = 0.0;
+    private static List<Object> listValue = null;
+    private static Map<String, Object> mapValue;
+
+    @MonkeyRunnerExported(doc = "", args = {"value"})
+    public static void floatTest(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        called = true;
+
+        floatValue = JythonUtils.getFloat(ap, 0);
+    }
+
+    @MonkeyRunnerExported(doc = "", args = {"value"})
+    public static void listTest(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        called = true;
+
+        listValue = JythonUtils.getList(ap, 0);
+    }
+
+    @MonkeyRunnerExported(doc = "", args = {"value"})
+    public static void mapTest(PyObject[] args, String[] kws) {
+        ArgParser ap = JythonUtils.createArgParser(args, kws);
+        Preconditions.checkNotNull(ap);
+        called = true;
+
+        mapValue = JythonUtils.getMap(ap, 0);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        called = false;
+        floatValue = 0.0;
+    }
+
+    private static void call(String method) {
+        call(method, "");
+    }
+    private static void call(String method, String... args) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("from ").append(PACKAGE_NAME);
+        sb.append(" import ").append(CLASS_NAME).append("\n");
+        sb.append(CLASS_NAME).append(".").append(method);
+        sb.append("(");
+        for (String arg : args) {
+            sb.append(arg).append(",");
+        }
+        sb.append(")");
+
+        ScriptRunner.runString(sb.toString());
+    }
+
+    public void testSimpleCall() {
+        call("floatTest", "0.0");
+        assertTrue(called);
+    }
+
+    public void testMissingFloatArg() {
+        try {
+            call("floatTest");
+        } catch(PyException e) {
+            return;
+        }
+        fail("Should have thrown exception");
+    }
+
+    public void testBadFloatArgType() {
+        try {
+            call("floatTest", "\'foo\'");
+        } catch(PyException e) {
+            return;
+        }
+        fail("Should have thrown exception");
+    }
+
+    public void testFloatParse() {
+        call("floatTest", "103.2");
+        assertTrue(called);
+        assertEquals(floatValue, 103.2);
+    }
+
+    public void testFloatParseInteger() {
+        call("floatTest", "103");
+        assertTrue(called);
+        assertEquals(floatValue, 103.0);
+    }
+
+    public void testParseStringList() {
+        call("listTest", "['a', 'b', 'c']");
+        assertTrue(called);
+        assertEquals(3, listValue.size());
+        assertEquals("a", listValue.get(0));
+        assertEquals("b", listValue.get(1));
+        assertEquals("c", listValue.get(2));
+    }
+
+    public void testParseIntList() {
+        call("listTest", "[1, 2, 3]");
+        assertTrue(called);
+        assertEquals(3, listValue.size());
+        assertEquals(new Integer(1), listValue.get(0));
+        assertEquals(new Integer(2), listValue.get(1));
+        assertEquals(new Integer(3), listValue.get(2));
+    }
+
+    public void testParseMixedList() {
+        call("listTest", "['a', 1, 3.14]");
+        assertTrue(called);
+        assertEquals(3, listValue.size());
+        assertEquals("a", listValue.get(0));
+        assertEquals(new Integer(1), listValue.get(1));
+        assertEquals(new Double(3.14), listValue.get(2));
+    }
+
+    public void testParsingNotAList() {
+        try {
+            call("listTest", "1.0");
+        } catch (PyException e) {
+            return;
+        }
+        fail("Should have thrown an exception");
+    }
+
+    public void testParseMap() {
+        call("mapTest", "{'a': 0, 'b': 'bee', 3: 'cee'}");
+        assertTrue(called);
+        assertEquals(3, mapValue.size());
+        assertEquals(new Integer(0), mapValue.get("a"));
+        assertEquals("bee", mapValue.get("b"));
+        // note: coerced key type
+        assertEquals("cee", mapValue.get("3"));
+    }
+
+    public void testParsingNotAMap() {
+        try {
+            call("mapTest", "1.0");
+        } catch (PyException e) {
+            return;
+        }
+        fail("Should have thrown an exception");
+    }
+}