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 \
package dalvik.runner;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
*/
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();
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");
}
/**
* 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);
+ 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);
*/
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);
}
}
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
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());