diff options
-rw-r--r-- | tools/runner/Android.mk | 2 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/DalvikRunner.java | 361 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/DeviceDalvikVm.java | 4 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/Driver.java | 13 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/JavaVm.java | 7 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/Option.java | 36 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/OptionParser.java | 443 | ||||
-rw-r--r-- | tools/runner/java/dalvik/runner/Vm.java | 10 |
8 files changed, 732 insertions, 144 deletions
diff --git a/tools/runner/Android.mk b/tools/runner/Android.mk index ee5c4f1..851214b 100644 --- a/tools/runner/Android.mk +++ b/tools/runner/Android.mk @@ -32,6 +32,8 @@ LOCAL_SRC_FILES := \ java/dalvik/runner/MainFinder.java \ java/dalvik/runner/MainRunner.java \ java/dalvik/runner/NamingPatternCodeFinder.java \ + java/dalvik/runner/Option.java \ + java/dalvik/runner/OptionParser.java \ java/dalvik/runner/Result.java \ java/dalvik/runner/Strings.java \ java/dalvik/runner/TestRun.java \ diff --git a/tools/runner/java/dalvik/runner/DalvikRunner.java b/tools/runner/java/dalvik/runner/DalvikRunner.java index 84d54ef..20e722c 100644 --- a/tools/runner/java/dalvik/runner/DalvikRunner.java +++ b/tools/runner/java/dalvik/runner/DalvikRunner.java @@ -17,6 +17,7 @@ package dalvik.runner; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; @@ -34,171 +35,271 @@ import java.util.logging.Logger; */ public final class DalvikRunner { - private final File localTemp; - private File sdkJar; - private Integer debugPort; - private long timeoutSeconds; - private Set<File> expectationFiles = new LinkedHashSet<File>(); - private File xmlReportsDirectory; - private String javaHome; - private List<String> vmArgs = new ArrayList<String>(); - private boolean clean = true; - private String deviceRunnerDir = "/sdcard/dalvikrunner"; - private List<File> testFiles = new ArrayList<File>(); - - private DalvikRunner() { - localTemp = new File("/tmp/" + UUID.randomUUID()); - timeoutSeconds = 10 * 60; // default is ten minutes - sdkJar = new File("/home/dalvik-prebuild/android-sdk-linux/platforms/android-2.0/android.jar"); - expectationFiles.add(new File("dalvik/libcore/tools/runner/expectations.txt")); - } + private static class Options { - private void prepareLogging() { - ConsoleHandler handler = new ConsoleHandler(); - handler.setLevel(Level.ALL); - handler.setFormatter(new Formatter() { - @Override public String format(LogRecord r) { - return r.getMessage() + "\n"; + private final List<File> testFiles = new ArrayList<File>(); + + @Option(names = { "--expectations" }) + private Set<File> expectationFiles = new LinkedHashSet<File>(); + { + expectationFiles.add(new File("dalvik/libcore/tools/runner/expectations.txt")); + } + + private static String MODE_DEVICE = "device"; + private static String MODE_HOST = "host"; + private static String MODE_ACTIVITY = "activity"; + @Option(names = { "--mode" }) + private String mode = MODE_DEVICE; + + @Option(names = { "--timeout" }) + private long timeoutSeconds = 10 * 60; // default is ten minutes; + + @Option(names = { "--clean" }) + private boolean clean = true; + + @Option(names = { "--xml-reports-directory" }) + private File xmlReportsDirectory; + + @Option(names = { "--verbose" }) + private boolean verbose; + + @Option(names = { "--debug" }) + private Integer debugPort; + + @Option(names = { "--device-runner-dir" }) + private File deviceRunnerDir = new File("/sdcard/dalvikrunner"); + + @Option(names = { "--vm-arg" }) + private List<String> vmArgs = new ArrayList<String>(); + + @Option(names = { "--java-home" }) + private File javaHome; + + @Option(names = { "--sdk" }) + private File sdkJar = new File("/home/dalvik-prebuild/android-sdk-linux/platforms/android-2.0/android.jar"); + + private void printUsage() { + System.out.println("Usage: DalvikRunner [options]... <tests>..."); + System.out.println(); + System.out.println(" <tests>: a .java file containing a jtreg test, JUnit test,"); + System.out.println(" Caliper benchmark, or a directory of such tests."); + System.out.println(); + System.out.println("GENERAL OPTIONS"); + System.out.println(); + System.out.println(" --expectations <file>: include the specified file when looking for"); + System.out.println(" test expectations. The file should include qualified test names"); + System.out.println(" and the corresponding expected output."); + System.out.println(" Default is: " + expectationFiles); + System.out.println(); + System.out.println(" --mode <device|host|activity>: specify which environment to run the"); + System.out.println(" tests in. Options are on the device VM, on the host VM, and on"); + System.out.println(" device within an android.app.Activity."); + System.out.println(" Default is: " + mode); + System.out.println(); + System.out.println(" --clean: remove temporary files (default). Disable with --no-clean"); + System.out.println(" and use with --verbose if you'd like to manually re-run"); + System.out.println(" commands afterwards."); + 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); + System.out.println(); + System.out.println(" --xml-reports-directory <path>: directory to emit JUnit-style"); + System.out.println(" XML test results."); + System.out.println(); + System.out.println(" --verbose: turn on verbose output"); + System.out.println(); + System.out.println("DEVICE OPTIONS"); + System.out.println(); + System.out.println(" --debug <port>: enable Java debugging on the specified port."); + System.out.println(" This port must be free both on the device and on the local"); + System.out.println(" system."); + System.out.println(); + System.out.println(" --device-runner-dir <directory>: use the specified directory for"); + System.out.println(" on-device temporary files and code."); + System.out.println(" Default is: " + deviceRunnerDir); + System.out.println(); + System.out.println("GENERAL VM OPTIONS"); + System.out.println(); + System.out.println(" --vm-arg <argument>: include the specified argument when spawning a"); + System.out.println(" virtual machine. Examples: -Xint:fast, -ea, -Xmx16M"); + System.out.println(); + System.out.println("HOST VM OPTIONS"); + System.out.println(); + System.out.println(" --java-home <java_home>: execute the tests on the local workstation"); + System.out.println(" using the specified java home directory. This does not impact"); + System.out.println(" which javac gets used. When unset, java is used from the PATH."); + System.out.println(); + System.out.println("COMPILE OPTIONS"); + System.out.println(); + System.out.println(" --sdk <android jar>: the API jar file to compile against."); + System.out.println(" Usually this is <SDK>/platforms/android-<X.X>/android.jar"); + System.out.println(" where <SDK> is the path to an Android SDK path and <X.X> is"); + System.out.println(" a release version like 1.5."); + System.out.println(" Default is: " + sdkJar); + System.out.println(); + } + + private boolean parseArgs(String[] args) { + final List<String> testFilenames; + try { + testFilenames = new OptionParser(this).parse(args); + } catch (RuntimeException e) { + System.out.println(e.getMessage()); + return false; } - }); - Logger logger = Logger.getLogger("dalvik.runner"); - logger.addHandler(handler); - logger.setUseParentHandlers(false); - } - private boolean parseArgs(String[] args) throws Exception { - for (int i = 0; i < args.length; i++) { - if ("--debug".equals(args[i])) { - debugPort = Integer.valueOf(args[++i]); + // + // Semantic error validation + // - } else if ("--device-runner-dir".equals(args[i])) { - deviceRunnerDir = args[++i]; + boolean device; + boolean vm; + if (mode.equals(MODE_DEVICE)) { + device = true; + vm = true; + } else if (mode.equals(MODE_HOST)) { + device = false; + vm = true; + } else if (mode.equals(MODE_ACTIVITY)) { + device = true; + vm = false; + } else { + System.out.println("Unknown mode: " + mode); + return false; + } - } else if ("--expectations".equals(args[i])) { - expectationFiles.add(new File(args[++i])); - } else if ("--java-home".equals(args[i])) { - javaHome = args[++i]; - if (!new File(javaHome, "/bin/java").exists()) { - System.out.println("Invalid java home: " + javaHome); + if (device) { // check device option consistency + if (javaHome != null) { + System.out.println("java home " + javaHome + " should not be specified for mode " + mode); return false; } - } else if ("--timeout-seconds".equals(args[i])) { - timeoutSeconds = Long.valueOf(args[++i]); + } else { // check host (!device) option consistency + if (javaHome != null && !new File(javaHome, "/bin/java").exists()) { + System.out.println("Invalid java home: " + javaHome); + return false; + } + if (debugPort != null) { + System.out.println("debug port " + debugPort + " should not be specified for mode " + mode); + return false; + } + } - } else if ("--sdk".equals(args[i])) { - sdkJar = new File(args[++i]); - if (!sdkJar.exists()) { - System.out.println("Could not find SDK jar: " + sdkJar); + // check vm option consistency + if (!vm) { + if (!vmArgs.isEmpty()) { + System.out.println("vm args " + vmArgs + " should not be specified for mode " + mode); return false; } + } - } else if ("--skip-clean".equals(args[i])) { - clean = false; + if (!sdkJar.exists()) { + System.out.println("Could not find SDK jar: " + sdkJar); + return false; + } - } else if ("--verbose".equals(args[i])) { - Logger.getLogger("dalvik.runner").setLevel(Level.FINE); + if (xmlReportsDirectory != null && !xmlReportsDirectory.isDirectory()) { + System.out.println("Invalid XML reports directory: " + xmlReportsDirectory); + return false; + } - } else if ("--vm-arg".equals(args[i])) { - vmArgs.add(args[++i]); + if (testFilenames.isEmpty()) { + System.out.println("No tests provided."); + return false; + } - } else if ("--xml-reports-directory".equals(args[i])) { - xmlReportsDirectory = new File(args[++i]); - if (!xmlReportsDirectory.isDirectory()) { - System.out.println("Invalid XML reports directory: " + xmlReportsDirectory); - return false; - } + // + // Post-processing arguments + // - } else if (args[i].startsWith("-")) { - System.out.println("Unrecognized option: " + args[i]); - return false; + for (String testFilename : testFilenames) { + testFiles.add(new File(testFilename)); + } - } else { - testFiles.add(new File(args[i])); + if (verbose) { + Logger.getLogger("dalvik.runner").setLevel(Level.FINE); } - } - if (testFiles.isEmpty()) { - System.out.println("No tests provided."); - return false; + return true; } - return true; } - private void printUsage() { - System.out.println("Usage: DalvikRunner [options]... <tests>..."); - System.out.println(); - System.out.println(" <tests>: a .java file containing a jtreg test, JUnit test,"); - System.out.println(" Caliper benchmark, or a directory of such tests."); - System.out.println(); - System.out.println("OPTIONS"); - System.out.println(); - System.out.println(" --debug <port>: enable Java debugging on the specified port."); - System.out.println(" This port must be free both on the device and on the local"); - System.out.println(" system."); - System.out.println(); - System.out.println(" ----device-runner-dir <directory>: use the specified directory for"); - System.out.println(" on-device temporary files and code."); - System.out.println(" Default is: " + deviceRunnerDir); - System.out.println(); - System.out.println(" --expectations <file>: include the specified file when looking for"); - System.out.println(" test expectations. The file should include qualified test names"); - System.out.println(" and the corresponding expected output."); - System.out.println(" Default is: " + expectationFiles); - System.out.println(); - System.out.println(" --java-home <java_home>: execute the tests on the local workstation"); - System.out.println(" using the specified java home directory. This does not impact"); - System.out.println(" which javac gets used. When unset, tests are run on a device"); - System.out.println(" using adb."); - System.out.println(); - System.out.println(" --sdk <android jar>: the API jar file to compile against."); - System.out.println(" Usually this is <SDK>/platforms/android-<X.X>/android.jar"); - System.out.println(" where <SDK> is the path to an Android SDK path and <X.X> is"); - System.out.println(" a release version like 1.5."); - System.out.println(" Default is: " + sdkJar); - System.out.println(); - System.out.println(" --skip-clean: leave temporary files in their place. Useful when"); - System.out.println(" coupled with --verbose if you'd like to manually re-run"); - System.out.println(" commands afterwards."); - 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); - System.out.println(); - System.out.println(" --vm-arg <argument>: include the specified argument when spawning a"); - System.out.println(" virtual machine. Examples: -Xint:fast, -ea, -Xmx16M"); - System.out.println(); - System.out.println(" --xml-reports-directory <path>: directory to emit JUnit-style"); - System.out.println(" XML test results."); - System.out.println(); - System.out.println(" --verbose: turn on verbose output"); - System.out.println(); + private final Options options = new Options(); + private final File localTemp = new File("/tmp/" + UUID.randomUUID()); + + private DalvikRunner() {} + + private void prepareLogging() { + ConsoleHandler handler = new ConsoleHandler(); + handler.setLevel(Level.ALL); + handler.setFormatter(new Formatter() { + @Override public String format(LogRecord r) { + return r.getMessage() + "\n"; + } + }); + Logger logger = Logger.getLogger("dalvik.runner"); + logger.addHandler(handler); + logger.setUseParentHandlers(false); } - private void run() throws Exception { - Vm vm = javaHome != null - ? new JavaVm(debugPort, timeoutSeconds, sdkJar, localTemp, - javaHome, vmArgs, clean) - : new DeviceDalvikVm(debugPort, timeoutSeconds, sdkJar, - localTemp, vmArgs, clean, deviceRunnerDir); + private void run() { + Vm vm; + if (options.mode.equals(Options.MODE_DEVICE)) { + vm = new DeviceDalvikVm( + options.debugPort, + options.timeoutSeconds, + options.sdkJar, + localTemp, + options.vmArgs, + options.clean, + options.deviceRunnerDir); + } else if (options.mode.equals(Options.MODE_HOST)) { + vm = new JavaVm( + options.debugPort, + options.timeoutSeconds, + options.sdkJar, + localTemp, + options.javaHome, + options.vmArgs, + options.clean); + } else if (options.mode.equals(Options.MODE_ACTIVITY)) { + vm = null; + System.out.println("Mode " + options.mode + " not currently supported."); + return; + } else { + System.out.println("Unknown mode mode " + options.mode + "."); + return; + } + List<CodeFinder> codeFinders = Arrays.asList( new JtregFinder(localTemp), new JUnitFinder(), new CaliperFinder(), new MainFinder()); - Driver driver = new Driver(localTemp, - vm, expectationFiles, xmlReportsDirectory, codeFinders); - driver.loadExpectations(); - driver.buildAndRunAllTests(testFiles); + Driver driver = new Driver( + localTemp, + vm, + options.expectationFiles, + options.xmlReportsDirectory, + codeFinders); + try { + driver.loadExpectations(); + } catch (IOException e) { + System.out.println("Problem loading expectations: " + e); + return; + } + + driver.buildAndRunAllTests(options.testFiles); vm.shutdown(); } - public static void main(String[] args) throws Exception { + public static void main(String[] args) { DalvikRunner dalvikRunner = new DalvikRunner(); - if (!dalvikRunner.parseArgs(args)) { - dalvikRunner.printUsage(); + if (!dalvikRunner.options.parseArgs(args)) { + dalvikRunner.options.printUsage(); return; } dalvikRunner.prepareLogging(); diff --git a/tools/runner/java/dalvik/runner/DeviceDalvikVm.java b/tools/runner/java/dalvik/runner/DeviceDalvikVm.java index 47db11f..4388565 100644 --- a/tools/runner/java/dalvik/runner/DeviceDalvikVm.java +++ b/tools/runner/java/dalvik/runner/DeviceDalvikVm.java @@ -38,10 +38,10 @@ final class DeviceDalvikVm extends Vm { private final Adb adb = new Adb(); DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar, - File localTemp, List<String> additionalVmArgs, boolean clean, String runnerDir) { + File localTemp, List<String> additionalVmArgs, boolean clean, File runnerDir) { super(debugPort, timeoutSeconds, sdkJar, localTemp, additionalVmArgs, clean); - this.runnerDir = new File(runnerDir); + this.runnerDir = runnerDir; this.testTemp = new File(this.runnerDir, "/tests.tmp"); } diff --git a/tools/runner/java/dalvik/runner/Driver.java b/tools/runner/java/dalvik/runner/Driver.java index 7a30ab7..81c7d6b 100644 --- a/tools/runner/java/dalvik/runner/Driver.java +++ b/tools/runner/java/dalvik/runner/Driver.java @@ -72,7 +72,7 @@ final class Driver { /** * Builds and executes all tests in the test directory. */ - public void buildAndRunAllTests(Collection<File> testFiles) throws Exception { + public void buildAndRunAllTests(Collection<File> testFiles) { localTemp.mkdirs(); final BlockingQueue<TestRun> readyToRun = new ArrayBlockingQueue<TestRun>(4); @@ -136,10 +136,15 @@ final class Driver { + readyToRun.size() + " are ready to run"); // if it takes 5 minutes for build and install, something is broken - TestRun testRun = readyToRun.poll(300, TimeUnit.SECONDS); + TestRun testRun; + try { + testRun = readyToRun.poll(5 * 60, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException("Unexpected interruption waiting for build and install", e); + } + if (testRun == null) { - throw new IllegalStateException( - "Expected " + tests.size() + " tests but found only " + i); + throw new IllegalStateException("Expected " + tests.size() + " tests but found only " + i); } runs.add(testRun); diff --git a/tools/runner/java/dalvik/runner/JavaVm.java b/tools/runner/java/dalvik/runner/JavaVm.java index 8b53477..e29b36b 100644 --- a/tools/runner/java/dalvik/runner/JavaVm.java +++ b/tools/runner/java/dalvik/runner/JavaVm.java @@ -24,18 +24,19 @@ import java.util.List; */ final class JavaVm extends Vm { - private final String javaHome; + private final File javaHome; JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp, - String javaHome, List<String> additionalVmArgs, boolean clean) { + File javaHome, List<String> additionalVmArgs, boolean clean) { super(debugPort, timeoutSeconds, sdkJar, localTemp, additionalVmArgs, clean); this.javaHome = javaHome; } @Override protected VmCommandBuilder newVmCommandBuilder( File workingDirectory) { + String java = javaHome == null ? "java" : new File(javaHome, "bin/java").getPath(); return new VmCommandBuilder() - .vmCommand(javaHome + "/bin/java") + .vmCommand(java) .workingDir(workingDirectory); } } diff --git a/tools/runner/java/dalvik/runner/Option.java b/tools/runner/java/dalvik/runner/Option.java new file mode 100644 index 0000000..779aa63 --- /dev/null +++ b/tools/runner/java/dalvik/runner/Option.java @@ -0,0 +1,36 @@ +/* + * 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 dalvik.runner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates a field as representing a command-line option for OptionParser. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Option { + /** + * The names for this option, such as { "-h", "--help" }. + * Names must start with one or two '-'s. + * An option must have at least one name. + */ + String[] names(); +} diff --git a/tools/runner/java/dalvik/runner/OptionParser.java b/tools/runner/java/dalvik/runner/OptionParser.java new file mode 100644 index 0000000..64af51c --- /dev/null +++ b/tools/runner/java/dalvik/runner/OptionParser.java @@ -0,0 +1,443 @@ +/* + * 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 dalvik.runner; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +/** + * Parses command line options. + * + * Strings in the passed-in String[] are parsed left-to-right. Each + * String is classified as a short option (such as "-v"), a long + * option (such as "--verbose"), an argument to an option (such as + * "out.txt" in "-f out.txt"), or a non-option positional argument. + * + * A simple short option is a "-" followed by a short option + * character. If the option requires an argument (which is true of any + * non-boolean option), it may be written as a separate parameter, but + * need not be. That is, "-f out.txt" and "-fout.txt" are both + * acceptable. + * + * It is possible to specify multiple short options after a single "-" + * as long as all (except possibly the last) do not require arguments. + * + * A long option begins with "--" followed by several characters. If + * the option requires an argument, it may be written directly after + * the option name, separated by "=", or as the next argument. (That + * is, "--file=out.txt" or "--file out.txt".) + * + * A boolean long option '--name' automatically gets a '--no-name' + * companion. Given an option "--flag", then, "--flag", "--no-flag", + * "--flag=true" and "--flag=false" are all valid, though neither + * "--flag true" nor "--flag false" are allowed (since "--flag" by + * itself is sufficient, the following "true" or "false" is + * interpreted separately). You can use "yes" and "no" as synonyms for + * "true" and "false". + * + * Each String not starting with a "-" and not a required argument of + * a previous option is a non-option positional argument, as are all + * successive Strings. Each String after a "--" is a non-option + * positional argument. + * + * Parsing of numeric fields such byte, short, int, long, float, and + * double fields is supported. This includes both unboxed and boxed + * versions (e.g. int vs Integer). If there is a problem parsing the + * argument to match the desired type, a runtime exception is thrown. + * + * File option fields are supported by simply wrapping the string + * argument in a File object without testing for the existance of the + * file. + * + * Parameterized Collection fields such as List<File> and Set<String> + * are supported as long as the parameter type is otherwise supported + * by the option parser. The collection field should be initialized + * with an appropriate collection instance. + * + * The fields corresponding to options are updated as their options + * are processed. Any remaining positional arguments are returned as a + * List<String>. + * + * Here's a simple example: + * + * // This doesn't need to be a separate class, if your application doesn't warrant it. + * // Non-@Option fields will be ignored. + * class Options { + * @Option(names = { "-q", "--quiet" }) + * boolean quiet = false; + * + * // Boolean options require a long name if it's to be possible to explicitly turn them off. + * // Here the user can use --no-color. + * @Option(names = { "--color" }) + * boolean color = true; + * + * @Option(names = { "-m", "--mode" }) + * String mode = "standard; // Supply a default just by setting the field. + * + * @Option(names = { "-p", "--port" }) + * int portNumber = 8888; + * + * // There's no need to offer a short name for rarely-used options. + * @Option(names = { "--timeout" }) + * double timeout = 1.0; + * + * @Option(names = { "-o", "--output-file" }) + * File output; + * + * // Multiple options are added to the collection. + * // The collection field itself must be non-null. + * @Option(names = { "-i", "--input-file" }) + * List<File> inputs = new ArrayList<File>(); + * + * } + * + * class Main { + * public static void main(String[] args) { + * Options options = new Options(); + * List<String> inputFilenames = new OptionParser(options).parse(args); + * for (String inputFilename : inputFilenames) { + * if (!options.quiet) { + * ... + * } + * ... + * } + * } + * } + * + * See also: + * + * the getopt(1) man page + * Python's "optparse" module (http://docs.python.org/library/optparse.html) + * the POSIX "Utility Syntax Guidelines" (http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap12.html#tag_12_02) + * the GNU "Standards for Command Line Interfaces" (http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces) + */ +public class OptionParser { + private static final HashMap<Class<?>, Handler> handlers = new HashMap<Class<?>, Handler>(); + static { + handlers.put(boolean.class, new BooleanHandler()); + handlers.put(Boolean.class, new BooleanHandler()); + + handlers.put(byte.class, new ByteHandler()); + handlers.put(Byte.class, new ByteHandler()); + handlers.put(short.class, new ShortHandler()); + handlers.put(Short.class, new ShortHandler()); + handlers.put(int.class, new IntegerHandler()); + handlers.put(Integer.class, new IntegerHandler()); + handlers.put(long.class, new LongHandler()); + handlers.put(Long.class, new LongHandler()); + + handlers.put(float.class, new FloatHandler()); + handlers.put(Float.class, new FloatHandler()); + handlers.put(double.class, new DoubleHandler()); + handlers.put(Double.class, new DoubleHandler()); + + handlers.put(String.class, new StringHandler()); + handlers.put(File.class, new FileHandler()); + } + Handler getHandler(Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class rawClass = (Class<?>) parameterizedType.getRawType(); + if (!Collection.class.isAssignableFrom(rawClass)) { + throw new RuntimeException("cannot handle non-collection parameterized type " + type); + } + Type actualType = parameterizedType.getActualTypeArguments()[0]; + if (!(actualType instanceof Class)) { + throw new RuntimeException("cannot handle nested parameterized type " + type); + } + return getHandler(actualType); + } + if (type instanceof Class) { + if (Collection.class.isAssignableFrom((Class) type)) { + // could handle by just having a default of treating + // contents as String but consciously decided this + // should be an error + throw new RuntimeException( + "cannot handle non-parameterized collection " + type + ". " + + "use a generic Collection to specify a desired element type"); + } + return handlers.get((Class<?>) type); + } + throw new RuntimeException("cannot handle unknown field type " + type); + } + + private final Object optionSource; + private final HashMap<String, Field> optionMap; + + /** + * Constructs a new OptionParser for setting the @Option fields of 'optionSource'. + */ + public OptionParser(Object optionSource) { + this.optionSource = optionSource; + this.optionMap = makeOptionMap(); + } + + /** + * Parses the command-line arguments 'args', setting the @Option fields of the 'optionSource' provided to the constructor. + * Returns a list of the positional arguments left over after processing all options. + */ + public List<String> parse(String[] args) { + return parseOptions(Arrays.asList(args).iterator()); + } + + private List<String> parseOptions(Iterator<String> args) { + final List<String> leftovers = new ArrayList<String>(); + + // Scan 'args'. + while (args.hasNext()) { + final String arg = args.next(); + if (arg.equals("--")) { + // "--" marks the end of options and the beginning of positional arguments. + break; + } else if (arg.startsWith("--")) { + // A long option. + parseLongOption(arg, args); + } else if (arg.startsWith("-")) { + // A short option. + parseGroupedShortOptions(arg, args); + } else { + // The first non-option marks the end of options. + leftovers.add(arg); + break; + } + } + + // Package up the leftovers. + while (args.hasNext()) { + leftovers.add(args.next()); + } + return leftovers; + } + + private Field fieldForArg(String name) { + final Field field = optionMap.get(name); + if (field == null) { + throw new RuntimeException("unrecognized option '" + name + "'"); + } + return field; + } + + private void parseLongOption(String arg, Iterator<String> args) { + String name = arg.replaceFirst("^--no-", "--"); + String value = null; + + // Support "--name=value" as well as "--name value". + final int equalsIndex = name.indexOf('='); + if (equalsIndex != -1) { + value = name.substring(equalsIndex + 1); + name = name.substring(0, equalsIndex); + } + + final Field field = fieldForArg(name); + final Handler handler = getHandler(field.getGenericType()); + if (value == null) { + if (handler.isBoolean()) { + value = arg.startsWith("--no-") ? "false" : "true"; + } else { + value = grabNextValue(args, name, field); + } + } + setValue(optionSource, field, arg, handler, value); + } + + // Given boolean options a and b, and non-boolean option f, we want to allow: + // -ab + // -abf out.txt + // -abfout.txt + // (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids it.) + private void parseGroupedShortOptions(String arg, Iterator<String> args) { + for (int i = 1; i < arg.length(); ++i) { + final String name = "-" + arg.charAt(i); + final Field field = fieldForArg(name); + final Handler handler = getHandler(field.getGenericType()); + String value; + if (handler.isBoolean()) { + value = "true"; + } else { + // We need a value. If there's anything left, we take the rest of this "short option". + if (i + 1 < arg.length()) { + value = arg.substring(i + 1); + i = arg.length() - 1; + } else { + value = grabNextValue(args, name, field); + } + } + setValue(optionSource, field, arg, handler, value); + } + } + + private static void setValue(Object object, Field field, String arg, Handler handler, String valueText) { + + Object value = handler.translate(valueText); + if (value == null) { + final String type = field.getType().getSimpleName().toLowerCase(); + throw new RuntimeException("couldn't convert '" + valueText + "' to a " + type + " for option '" + arg + "'"); + } + try { + field.setAccessible(true); + if (Collection.class.isAssignableFrom(field.getType())) { + Collection collection = (Collection)field.get(object); + collection.add(value); + } else { + field.set(object, value); + } + } catch (IllegalAccessException ex) { + throw new RuntimeException("internal error", ex); + } + } + + // Returns the next element of 'args' if there is one. Uses 'name' and 'field' to construct a helpful error message. + private String grabNextValue(Iterator<String> args, String name, Field field) { + if (!args.hasNext()) { + final String type = field.getType().getSimpleName().toLowerCase(); + throw new RuntimeException("option '" + name + "' requires a " + type + " argument"); + } + return args.next(); + } + + // Cache the available options and report any problems with the options themselves right away. + private HashMap<String, Field> makeOptionMap() { + final HashMap<String, Field> optionMap = new HashMap<String, Field>(); + final Class<?> optionClass = optionSource.getClass(); + for (Field field : optionClass.getDeclaredFields()) { + if (field.isAnnotationPresent(Option.class)) { + final Option option = field.getAnnotation(Option.class); + final String[] names = option.names(); + if (names.length == 0) { + throw new RuntimeException("found an @Option with no name!"); + } + for (String name : names) { + if (optionMap.put(name, field) != null) { + throw new RuntimeException("found multiple @Options sharing the name '" + name + "'"); + } + } + if (getHandler(field.getGenericType()) == null) { + throw new RuntimeException("unsupported @Option field type '" + field.getType() + "'"); + } + } + } + return optionMap; + } + + static abstract class Handler { + // Only BooleanHandler should ever override this. + boolean isBoolean() { + return false; + } + + /** + * Returns an object of appropriate type for the given Handle, corresponding to 'valueText'. + * Returns null on failure. + */ + abstract Object translate(String valueText); + } + + static class BooleanHandler extends Handler { + @Override boolean isBoolean() { + return true; + } + + Object translate(String valueText) { + if (valueText.equalsIgnoreCase("true") || valueText.equalsIgnoreCase("yes")) { + return Boolean.TRUE; + } else if (valueText.equalsIgnoreCase("false") || valueText.equalsIgnoreCase("no")) { + return Boolean.FALSE; + } + return null; + } + } + + static class ByteHandler extends Handler { + Object translate(String valueText) { + try { + return Byte.parseByte(valueText); + } catch (NumberFormatException ex) { + return null; + } + } + } + + static class ShortHandler extends Handler { + Object translate(String valueText) { + try { + return Short.parseShort(valueText); + } catch (NumberFormatException ex) { + return null; + } + } + } + + static class IntegerHandler extends Handler { + Object translate(String valueText) { + try { + return Integer.parseInt(valueText); + } catch (NumberFormatException ex) { + return null; + } + } + } + + static class LongHandler extends Handler { + Object translate(String valueText) { + try { + return Long.parseLong(valueText); + } catch (NumberFormatException ex) { + return null; + } + } + } + + static class FloatHandler extends Handler { + Object translate(String valueText) { + try { + return Float.parseFloat(valueText); + } catch (NumberFormatException ex) { + return null; + } + } + } + + static class DoubleHandler extends Handler { + Object translate(String valueText) { + try { + return Double.parseDouble(valueText); + } catch (NumberFormatException ex) { + return null; + } + } + } + + static class StringHandler extends Handler { + Object translate(String valueText) { + return valueText; + } + } + + static class FileHandler extends Handler { + Object translate(String valueText) { + return new File(valueText); + } + } +} diff --git a/tools/runner/java/dalvik/runner/Vm.java b/tools/runner/java/dalvik/runner/Vm.java index 3d66fce..3afd7ae 100644 --- a/tools/runner/java/dalvik/runner/Vm.java +++ b/tools/runner/java/dalvik/runner/Vm.java @@ -51,11 +51,11 @@ public abstract class Vm { private final Pattern JAVA_TEST_PATTERN = Pattern.compile("\\/(\\w)+\\.java$"); static final Classpath COMPILATION_CLASSPATH = Classpath.of( - new File("out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar"), - new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar"), - new File("out/target/common/obj/JAVA_LIBRARIES/jsr305_intermediates/classes.jar"), - new File("out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/classes.jar"), - new File("out/target/common/obj/JAVA_LIBRARIES/caliper_intermediates/classes.jar")); + new File("out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar").getAbsoluteFile(), + new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar").getAbsoluteFile(), + new File("out/target/common/obj/JAVA_LIBRARIES/jsr305_intermediates/classes.jar").getAbsoluteFile(), + new File("out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/classes.jar").getAbsoluteFile(), + new File("out/target/common/obj/JAVA_LIBRARIES/caliper_intermediates/classes.jar").getAbsoluteFile()); private static final Logger logger = Logger.getLogger(Vm.class.getName()); |