Add in "plugin" support to allow users to add their own plugin's via JAR files and the -plugin command line argument.
Also make sure that the arguments passed on the command line correctly get set in sys.argv.
Change-Id: I35014adc95ac9e5e5c777dc17749ee2b9b155c57
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
index 85dbc7c..d144780 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java
@@ -91,8 +91,11 @@
@MonkeyRunnerExported(doc = "Simple help command to dump the MonkeyRunner supported " +
"commands",
returns = "The help text")
- public static String help(PyObject[] args, String[] kws) {
- return MonkeyRunnerHelp.helpString();
+ public static String help(PyObject[] args, String[] kws) {
+ ArgParser ap = JythonUtils.createArgParser(args, kws);
+ Preconditions.checkNotNull(ap);
+
+ return MonkeyRunnerHelp.helpString();
}
@MonkeyRunnerExported(doc = "Put up an alert dialog to inform the user of something that " +
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunningOptions.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
similarity index 65%
rename from tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunningOptions.java
rename to tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
index 3215c31..68577a5 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunningOptions.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java
@@ -15,12 +15,15 @@
*/
package com.android.monkeyrunner;
+import com.google.common.collect.ImmutableList;
+
import java.io.File;
+import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
-public class MonkeyRunningOptions {
- private static final Logger LOG = Logger.getLogger(MonkeyRunningOptions.class.getName());
+public class MonkeyRunnerOptions {
+ private static final Logger LOG = Logger.getLogger(MonkeyRunnerOptions.class.getName());
private static String DEFAULT_MONKEY_SERVER_ADDRESS = "127.0.0.1";
private static int DEFAULT_MONKEY_PORT = 12345;
@@ -28,12 +31,17 @@
private final String hostname;
private final File scriptFile;
private final String backend;
+ private final Collection<File> plugins;
+ private final Collection<String> arguments;
- private MonkeyRunningOptions(String hostname, int port, File scriptFile, String backend) {
+ private MonkeyRunnerOptions(String hostname, int port, File scriptFile, String backend,
+ Collection<File> plugins, Collection<String> arguments) {
this.hostname = hostname;
this.port = port;
this.scriptFile = scriptFile;
this.backend = backend;
+ this.plugins = plugins;
+ this.arguments = arguments;
}
public int getPort() {
@@ -52,6 +60,14 @@
return backend;
}
+ public Collection<File> getPlugins() {
+ return plugins;
+ }
+
+ public Collection<String> getArguments() {
+ return arguments;
+ }
+
private static void printUsage(String message) {
System.out.println(message);
System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
@@ -68,7 +84,7 @@
*
* @return the parsed options, or null if there was an error.
*/
- public static MonkeyRunningOptions processOptions(String[] args) {
+ public static MonkeyRunnerOptions processOptions(String[] args) {
// parse command line parameters.
int index = 0;
@@ -77,6 +93,8 @@
int port = DEFAULT_MONKEY_PORT;
String backend = "adb";
+ ImmutableList.Builder<File> pluginListBuilder = ImmutableList.builder();
+ ImmutableList.Builder<String> argumentBuilder = ImmutableList.builder();
do {
String argument = args[index++];
@@ -114,16 +132,42 @@
return null;
}
backend = args[index++];
+ } else if ("-plugin".equals(argument)) {
+ // quick check on the next argument.
+ if (index == args.length) {
+ printUsage("Missing plugin path after -plugin");
+ return null;
+ }
+ File plugin = new File(args[index++]);
+ if (!plugin.exists()) {
+ printUsage("Plugin file doesn't exist");
+ return null;
+ }
+
+ if (!plugin.canRead()) {
+ printUsage("Can't read plugin file");
+ return null;
+ }
+
+ pluginListBuilder.add(plugin);
} else if (argument.startsWith("-")) {
// we have an unrecognized argument.
printUsage("Unrecognized argument: " + argument + ".");
return null;
} else {
- // get the filepath of the script to run. This will be the last undashed argument.
- scriptFile = new File(argument);
- if (!scriptFile.exists()) {
- printUsage("Can't open specified script file");
- return null;
+ if (scriptFile == null) {
+ // get the filepath of the script to run. This will be the last undashed argument.
+ scriptFile = new File(argument);
+ if (!scriptFile.exists()) {
+ printUsage("Can't open specified script file");
+ return null;
+ }
+ if (!scriptFile.canRead()) {
+ printUsage("Can't open specified script file");
+ return null;
+ }
+ } else {
+ argumentBuilder.add(argument);
}
}
} while (index < args.length);
@@ -133,6 +177,7 @@
return null;
}
- return new MonkeyRunningOptions(hostname, port, scriptFile, backend);
+ return new MonkeyRunnerOptions(hostname, port, scriptFile, backend,
+ pluginListBuilder.build(), argumentBuilder.build());
}
}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
index e2fdee9..54bb7da 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java
@@ -15,10 +15,25 @@
*/
package com.android.monkeyrunner;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+
import com.android.monkeyrunner.adb.AdbBackend;
+import com.android.monkeyrunner.stub.StubBackend;
+
+import org.python.util.PythonInterpreter;
import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
import java.util.Enumeration;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
@@ -33,9 +48,19 @@
*/
public class MonkeyRunnerStarter {
private static final Logger LOG = Logger.getLogger(MonkeyRunnerStarter.class.getName());
+ private static final String MONKEY_RUNNER_MAIN_MANIFEST_NAME = "MonkeyRunnerStartupRunner";
private final MonkeyRunnerBackend backend;
- private final File scriptFile;
+ private final MonkeyRunnerOptions options;
+
+ public MonkeyRunnerStarter(MonkeyRunnerOptions options) {
+ this.options = options;
+ this.backend = MonkeyRunnerStarter.createBackendByName(options.getBackendName());
+ if (this.backend == null) {
+ throw new RuntimeException("Unknown backend");
+ }
+ }
+
/**
* Creates a specific backend by name.
@@ -43,30 +68,103 @@
* @param backendName the name of the backend to create
* @return the new backend, or null if none were found.
*/
- public MonkeyRunnerBackend createBackendByName(String backendName) {
+ public static MonkeyRunnerBackend createBackendByName(String backendName) {
if ("adb".equals(backendName)) {
return new AdbBackend();
+ } else if ("stub".equals(backendName)) {
+ return new StubBackend();
} else {
return null;
}
}
- public MonkeyRunnerStarter(String backendName,
- File scriptFile) {
- this.backend = createBackendByName(backendName);
- if (this.backend == null) {
- throw new RuntimeException("Unknown backend");
- }
- this.scriptFile = scriptFile;
- }
-
private void run() {
MonkeyRunner.setBackend(backend);
- ScriptRunner.run(scriptFile.getAbsolutePath());
+ Map<String, Predicate<PythonInterpreter>> plugins = handlePlugins();
+ ScriptRunner.run(options.getScriptFile().getAbsolutePath(),
+ options.getArguments(), plugins);
backend.shutdown();
MonkeyRunner.setBackend(null);
}
+ private Predicate<PythonInterpreter> handlePlugin(File f) {
+ JarFile jarFile;
+ try {
+ jarFile = new JarFile(f);
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Unable to open plugin file. Is it a jar file? " +
+ f.getAbsolutePath(), e);
+ return Predicates.alwaysFalse();
+ }
+ Manifest manifest;
+ try {
+ manifest = jarFile.getManifest();
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "Unable to get manifest file from jar: " +
+ f.getAbsolutePath(), e);
+ return Predicates.alwaysFalse();
+ }
+ Attributes mainAttributes = manifest.getMainAttributes();
+ String pluginClass = mainAttributes.getValue(MONKEY_RUNNER_MAIN_MANIFEST_NAME);
+ if (pluginClass == null) {
+ // No main in this plugin, so it always succeeds.
+ return Predicates.alwaysTrue();
+ }
+ URL url;
+ try {
+ url = f.toURI().toURL();
+ } catch (MalformedURLException e) {
+ LOG.log(Level.SEVERE, "Unable to convert file to url " + f.getAbsolutePath(),
+ e);
+ return Predicates.alwaysFalse();
+ }
+ URLClassLoader classLoader = new URLClassLoader(new URL[] { url },
+ ClassLoader.getSystemClassLoader());
+ Class<?> clz;
+ try {
+ clz = Class.forName(pluginClass, true, classLoader);
+ } catch (ClassNotFoundException e) {
+ LOG.log(Level.SEVERE, "Unable to load the specified plugin: " + pluginClass, e);
+ return Predicates.alwaysFalse();
+ }
+ Object loadedObject;
+ try {
+ loadedObject = clz.newInstance();
+ } catch (InstantiationException e) {
+ LOG.log(Level.SEVERE, "Unable to load the specified plugin: " + pluginClass, e);
+ return Predicates.alwaysFalse();
+ } catch (IllegalAccessException e) {
+ LOG.log(Level.SEVERE, "Unable to load the specified plugin " +
+ "(did you make it public?): " + pluginClass, e);
+ return Predicates.alwaysFalse();
+ }
+ // Cast it to the right type
+ if (loadedObject instanceof Runnable) {
+ final Runnable run = (Runnable) loadedObject;
+ return new Predicate<PythonInterpreter>() {
+ public boolean apply(PythonInterpreter i) {
+ run.run();
+ return true;
+ }
+ };
+ } else if (loadedObject instanceof Predicate<?>) {
+ return (Predicate<PythonInterpreter>) loadedObject;
+ } else {
+ LOG.severe("Unable to coerce object into correct type: " + pluginClass);
+ return Predicates.alwaysFalse();
+ }
+ }
+
+ private Map<String, Predicate<PythonInterpreter>> handlePlugins() {
+ ImmutableMap.Builder<String, Predicate<PythonInterpreter>> builder = ImmutableMap.builder();
+ for (File f : options.getPlugins()) {
+ builder.put(f.getAbsolutePath(), handlePlugin(f));
+ }
+ return builder.build();
+ }
+
+
+
private static final void replaceAllLogFormatters(Formatter form) {
LogManager mgr = LogManager.getLogManager();
Enumeration<String> loggerNames = mgr.getLoggerNames();
@@ -81,7 +179,7 @@
}
public static void main(String[] args) {
- MonkeyRunningOptions options = MonkeyRunningOptions.processOptions(args);
+ MonkeyRunnerOptions options = MonkeyRunnerOptions.processOptions(args);
// logging property files are difficult
replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE);
@@ -90,8 +188,7 @@
return;
}
- MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options.getBackendName(),
- options.getScriptFile());
+ MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options);
runner.run();
// This will kill any background threads as well.
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
index c027be8..7920e50 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java
@@ -15,7 +15,10 @@
*/
package com.android.monkeyrunner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableMap.Builder;
import org.python.core.PyObject;
import org.python.util.InteractiveConsole;
@@ -23,15 +26,21 @@
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Runs Jython based scripts.
*/
public class ScriptRunner {
+ private static final Logger LOG = Logger.getLogger(MonkeyRunnerOptions.class.getName());
/** The "this" scope object for scripts. */
private final Object scope;
@@ -51,20 +60,46 @@
/**
* Runs the specified Jython script. First runs the initialization script to
* preload the appropriate client library version.
+ *
+ * @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.
*/
- public static void run(String scriptfilename) {
- try {
- // 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() });
+ public static void 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);
- PythonInterpreter python = new PythonInterpreter();
+ // Adjust the classpath so jython can access the classes in the specified classpath.
+ Collection<String> classpath = Lists.newArrayList(f.getParent());
+ classpath.addAll(plugins.keySet());
- python.execfile(scriptfilename);
- } catch(Exception e) {
- e.printStackTrace();
+ String[] argv = new String[args.size() + 1];
+ argv[0] = f.getAbsolutePath();
+ int x = 1;
+ for (String arg : args) {
+ argv[x++] = arg;
}
+
+ initPython(classpath, argv);
+
+ PythonInterpreter python = new PythonInterpreter();
+
+ // Now let the mains run.
+ for (Map.Entry<String, Predicate<PythonInterpreter>> entry : plugins.entrySet()) {
+ boolean success;
+ try {
+ success = entry.getValue().apply(python);
+ } catch (Exception e) {
+ LOG.log(Level.SEVERE, "Plugin Main through an exception.", e);
+ continue;
+ }
+ if (!success) {
+ LOG.severe("Plugin Main returned error for: " + entry.getKey());
+ }
+ }
+
+ python.execfile(scriptfilename);
}
public static void runString(String script) {
@@ -73,11 +108,20 @@
python.exec(script);
}
- public static PyObject runStringAndGet(String script, String name) {
+ public static Map<String, PyObject> runStringAndGet(String script, String... names) {
+ return runStringAndGet(script, Arrays.asList(names));
+ }
+
+ public static Map<String, PyObject> runStringAndGet(String script, Collection<String> names) {
initPython();
- PythonInterpreter python = new PythonInterpreter();
+ final PythonInterpreter python = new PythonInterpreter();
python.exec(script);
- return python.get(name);
+
+ Builder<String, PyObject> builder = ImmutableMap.builder();
+ for (String name : names) {
+ builder.put(name, python.get(name));
+ }
+ return builder.build();
}
private static void initPython() {
@@ -85,7 +129,7 @@
initPython(arg, new String[] {""});
}
- private static void initPython(List<String> pythonPath,
+ private static void initPython(Collection<String> pythonPath,
String[] argv) {
Properties props = new Properties();
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/exceptions/MonkeyRunnerException.java b/tools/monkeyrunner/src/com/android/monkeyrunner/exceptions/MonkeyRunnerException.java
new file mode 100644
index 0000000..1d5f19b
--- /dev/null
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/exceptions/MonkeyRunnerException.java
@@ -0,0 +1,18 @@
+package com.android.monkeyrunner.exceptions;
+
+/**
+ * Base exception class for all MonkeyRunner Exceptions.
+ */
+public class MonkeyRunnerException extends Exception {
+ public MonkeyRunnerException(String message) {
+ super(message);
+ }
+
+ public MonkeyRunnerException(Throwable e) {
+ super(e);
+ }
+
+ public MonkeyRunnerException(String message, Throwable e) {
+ super(message, e);
+ }
+}
diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java b/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
index 7edff69..9b23bab 100644
--- a/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
+++ b/tools/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java
@@ -4,19 +4,15 @@
import com.android.monkeyrunner.MonkeyManager;
import com.android.monkeyrunner.MonkeyRunnerBackend;
-/**
- * This is a stub backend that doesn't do anything at all. Useful for
- * running unit tests.
- */
public class StubBackend implements MonkeyRunnerBackend {
public MonkeyManager createManager(String address, int port) {
- // We're stub - we've got nothing to do.
+ // TODO Auto-generated method stub
return null;
}
public MonkeyDevice waitForConnection(long timeout, String deviceId) {
- // We're stub - we've got nothing to do.
+ // TODO Auto-generated method stub
return null;
}
diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
index 6e3260d..5b8c8f9 100644
--- a/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
+++ b/tools/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java
@@ -102,7 +102,7 @@
}
sb.append(")");
- return ScriptRunner.runStringAndGet(sb.toString(), "result");
+ return ScriptRunner.runStringAndGet(sb.toString(), "result").get("result");
}
public void testSimpleCall() {