Adds more testability to our Main classes.
Adds options testing to them also.

Review by: bobv (TBR)

git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/1.6@4350 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index 49a239d..a03250b 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -42,11 +42,17 @@
    * Handles the list of startup urls that can be passed at the end of the
    * command line.
    */
-  protected class ArgHandlerStartupURLsExtra extends ArgHandlerExtra {
+  protected static class ArgHandlerStartupURLsExtra extends ArgHandlerExtra {
+
+    private final OptionStartupURLs options;
+
+    public ArgHandlerStartupURLsExtra(OptionStartupURLs options) {
+      this.options = options;
+    }
 
     @Override
     public boolean addExtraArg(String arg) {
-      addStartupURL(arg);
+      options.addStartupURL(arg);
       return true;
     }
 
@@ -64,14 +70,12 @@
   /**
    * The GWTShell argument processor.
    */
-  protected class ArgProcessor extends HostedModeBase.ArgProcessor {
-    public ArgProcessor(boolean forceServer, boolean noURLs) {
-      if (!forceServer) {
-        registerHandler(new ArgHandlerNoServerFlag());
-      }
-
+  protected static class ArgProcessor extends HostedModeBase.ArgProcessor {
+    public ArgProcessor(ShellOptionsImpl options, boolean forceServer,
+        boolean noURLs) {
+      super(options, forceServer);
       if (!noURLs) {
-        registerHandler(new ArgHandlerStartupURLsExtra());
+        registerHandler(new ArgHandlerStartupURLsExtra(options));
       }
       registerHandler(new ArgHandlerOutDir(options));
     }
@@ -83,16 +87,23 @@
   }
 
   /**
-   * Concrete class to implement all compiler options.
+   * Concrete class to implement all shell options.
    */
-  static class ShellOptionsImpl extends GWTCompilerOptionsImpl implements
-      HostedModeBaseOptions, WorkDirs {
+  static class ShellOptionsImpl extends HostedModeBaseOptionsImpl implements
+      HostedModeBaseOptions, WorkDirs, LegacyCompilerOptions {
+    private int localWorkers;
+    private File outDir;
+
     public File getCompilerOutputDir(ModuleDef moduleDef) {
       return new File(getOutDir(), moduleDef.getName());
     }
 
-    public File getShellBaseWorkDir(ModuleDef moduleDef) {
-      return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
+    public int getLocalWorkers() {
+      return localWorkers;
+    }
+
+    public File getOutDir() {
+      return outDir;
     }
 
     public File getShellPublicGenDir(ModuleDef moduleDef) {
@@ -103,6 +114,14 @@
     public File getWorkDir() {
       return new File(getOutDir(), ".gwt-tmp");
     }
+
+    public void setLocalWorkers(int localWorkers) {
+      this.localWorkers = localWorkers;
+    }
+
+    public void setOutDir(File outDir) {
+      this.outDir = outDir;
+    }
   }
 
   public static void main(String[] args) {
@@ -115,9 +134,10 @@
      * shutdown AWT related threads, since the contract for their termination is
      * still implementation-dependent.
      */
-    GWTShell shellMain = new GWTShell();
-    if (shellMain.new ArgProcessor(false, false).processArgs(args)) {
-      shellMain.run();
+    GWTShell gwtShell = new GWTShell();
+    ArgProcessor argProcessor = new ArgProcessor(gwtShell.options, false, false);
+    if (argProcessor.processArgs(args)) {
+      gwtShell.run();
       // Exit w/ success code.
       System.exit(0);
     }
diff --git a/dev/core/src/com/google/gwt/dev/HostedMode.java b/dev/core/src/com/google/gwt/dev/HostedMode.java
index 93ebb2c..19dba6b 100644
--- a/dev/core/src/com/google/gwt/dev/HostedMode.java
+++ b/dev/core/src/com/google/gwt/dev/HostedMode.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.shell.jetty.JettyLauncher;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerExtraDir;
+import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
 import com.google.gwt.dev.util.arg.ArgHandlerModuleName;
 import com.google.gwt.dev.util.arg.ArgHandlerWarDir;
 import com.google.gwt.dev.util.arg.ArgHandlerWorkDirOptional;
@@ -51,7 +52,22 @@
   /**
    * Handles the -server command line flag.
    */
-  protected class ArgHandlerServer extends ArgHandlerString {
+  protected static class ArgHandlerServer extends ArgHandlerString {
+    private HostedModeOptions options;
+
+    public ArgHandlerServer(HostedModeOptions options) {
+      this.options = options;
+    }
+
+    @Override
+    public String[] getDefaultArgs() {
+      if (options.isNoServer()) {
+        return null;
+      } else {
+        return new String[] {getTag(), JettyLauncher.class.getName()};
+      }
+    }
+
     @Override
     public String getPurpose() {
       return "Prevents the embedded Tomcat server from running, even if a port is specified";
@@ -64,21 +80,44 @@
 
     @Override
     public String[] getTagArgs() {
-      return new String[] {"serverLauncherClass"};
+      return new String[] {"servletContainerLauncher"};
     }
 
     @Override
-    public boolean setString(String arg) {
+    public boolean setString(String sclClassName) {
       // Supercedes -noserver.
-      setRunTomcat(true);
-      return setServer(console, arg);
+      options.setNoServer(false);
+      Throwable t;
+      try {
+        Class<?> clazz = Class.forName(sclClassName, true,
+            Thread.currentThread().getContextClassLoader());
+        Class<? extends ServletContainerLauncher> sclClass = clazz.asSubclass(ServletContainerLauncher.class);
+        options.setServletContainerLauncher(sclClass.newInstance());
+        return true;
+      } catch (ClassCastException e) {
+        t = e;
+      } catch (ClassNotFoundException e) {
+        t = e;
+      } catch (InstantiationException e) {
+        t = e;
+      } catch (IllegalAccessException e) {
+        t = e;
+      }
+      System.err.println("Unable to load server class '" + sclClassName + "'");
+      t.printStackTrace();
+      return false;
     }
   }
 
   /**
    * Handles a startup url that can be passed on the command line.
    */
-  protected class ArgHandlerStartupURLs extends ArgHandlerString {
+  protected static class ArgHandlerStartupURLs extends ArgHandlerString {
+    private final OptionStartupURLs options;
+
+    public ArgHandlerStartupURLs(OptionStartupURLs options) {
+      this.options = options;
+    }
 
     @Override
     public String getPurpose() {
@@ -97,19 +136,20 @@
 
     @Override
     public boolean setString(String arg) {
-      addStartupURL(arg);
+      options.addStartupURL(arg);
       return true;
     }
   }
 
-  class ArgProcessor extends HostedModeBase.ArgProcessor {
-    public ArgProcessor() {
-      registerHandler(new ArgHandlerServer());
-      registerHandler(new ArgHandlerNoServerFlag());
-      registerHandler(new ArgHandlerStartupURLs());
+  static class ArgProcessor extends HostedModeBase.ArgProcessor {
+    public ArgProcessor(HostedModeOptions options) {
+      super(options, false);
+      registerHandler(new ArgHandlerServer(options));
+      registerHandler(new ArgHandlerStartupURLs(options));
       registerHandler(new ArgHandlerWarDir(options));
       registerHandler(new ArgHandlerExtraDir(options));
       registerHandler(new ArgHandlerWorkDirOptional(options));
+      registerHandler(new ArgHandlerLocalWorkers(options));
       registerHandler(new ArgHandlerModuleName(options));
     }
 
@@ -119,14 +159,61 @@
     }
   }
 
+  interface HostedModeOptions extends HostedModeBaseOptions, CompilerOptions {
+    ServletContainerLauncher getServletContainerLauncher();
+
+    void setServletContainerLauncher(ServletContainerLauncher scl);
+  }
+
   /**
-   * Concrete class to implement all compiler options.
+   * Concrete class to implement all hosted mode options.
    */
-  static class HostedModeOptionsImpl extends CompilerOptionsImpl implements
-      HostedModeBaseOptions {
+  static class HostedModeOptionsImpl extends HostedModeBaseOptionsImpl
+      implements HostedModeOptions {
+    private File extraDir;
+    private int localWorkers;
+    private ServletContainerLauncher scl;
+    private File warDir;
+
+    public File getExtraDir() {
+      return extraDir;
+    }
+
+    public int getLocalWorkers() {
+      return localWorkers;
+    }
+
+    public ServletContainerLauncher getServletContainerLauncher() {
+      return scl;
+    }
+
     public File getShellBaseWorkDir(ModuleDef moduleDef) {
       return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
     }
+
+    public File getShellPublicGenDir(ModuleDef moduleDef) {
+      return new File(getShellBaseWorkDir(moduleDef), "public");
+    }
+
+    public File getWarDir() {
+      return warDir;
+    }
+
+    public void setExtraDir(File extraDir) {
+      this.extraDir = extraDir;
+    }
+
+    public void setLocalWorkers(int localWorkers) {
+      this.localWorkers = localWorkers;
+    }
+
+    public void setServletContainerLauncher(ServletContainerLauncher scl) {
+      this.scl = scl;
+    }
+
+    public void setWarDir(File warDir) {
+      this.warDir = warDir;
+    }
   }
 
   public static void main(String[] args) {
@@ -137,7 +224,7 @@
      * still implementation-dependent.
      */
     HostedMode hostedMode = new HostedMode();
-    if (hostedMode.new ArgProcessor().processArgs(args)) {
+    if (new ArgProcessor(hostedMode.options).processArgs(args)) {
       hostedMode.run();
       // Exit w/ success code.
       System.exit(0);
@@ -157,11 +244,6 @@
   protected final HostedModeOptionsImpl options = (HostedModeOptionsImpl) super.options;
 
   /**
-   * The servlet launcher to use (defaults to embedded Jetty).
-   */
-  private ServletContainerLauncher launcher = new JettyLauncher();
-
-  /**
    * Maps each active linker stack by module.
    */
   private final Map<String, StandardLinkerContext> linkerStacks = new HashMap<String, StandardLinkerContext>();
@@ -187,9 +269,9 @@
   }
 
   /**
-   * The public API of this class is yet to be determined.
+   * Default constructor for testing; no public API yet.
    */
-  private HostedMode() {
+  HostedMode() {
   }
 
   @Override
@@ -278,7 +360,8 @@
     try {
       TreeLogger serverLogger = getTopLogger().branch(TreeLogger.INFO,
           "Starting HTTP on port " + getPort(), null);
-      server = launcher.start(serverLogger, getPort(), options.getWarDir());
+      server = options.getServletContainerLauncher().start(serverLogger,
+          getPort(), options.getWarDir());
       assert (server != null);
       return server.getPort();
     } catch (BindException e) {
@@ -293,6 +376,14 @@
   }
 
   @Override
+  protected String getHost() {
+    if (server != null) {
+      return server.getHost();
+    }
+    return super.getHost();
+  }
+
+  @Override
   protected boolean initModule(String moduleName) {
     ModuleDef module = modulesByName.get(moduleName);
     if (module == null) {
@@ -328,28 +419,6 @@
     return module;
   }
 
-  protected boolean setServer(TreeLogger logger, String serverClassName) {
-    Throwable t;
-    try {
-      Class<?> clazz = Class.forName(serverClassName, true,
-          Thread.currentThread().getContextClassLoader());
-      Class<? extends ServletContainerLauncher> sclClass = clazz.asSubclass(ServletContainerLauncher.class);
-      launcher = sclClass.newInstance();
-      return true;
-    } catch (ClassCastException e) {
-      t = e;
-    } catch (ClassNotFoundException e) {
-      t = e;
-    } catch (InstantiationException e) {
-      t = e;
-    } catch (IllegalAccessException e) {
-      t = e;
-    }
-    logger.log(TreeLogger.ERROR, "Unable to load server class '"
-        + serverClassName + "'", t);
-    return false;
-  }
-
   /**
    * Perform an initial hosted mode link, without overwriting newer or
    * unmodified files in the output folder.
diff --git a/dev/core/src/com/google/gwt/dev/HostedModeBase.java b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
index ab72df7..331297a 100644
--- a/dev/core/src/com/google/gwt/dev/HostedModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/HostedModeBase.java
@@ -17,8 +17,8 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.Precompile.PrecompileOptionsImpl;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.jjs.JJSOptions;
@@ -56,6 +56,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -68,7 +69,7 @@
   /**
    * Handles the -blacklist command line argument.
    */
-  protected class ArgHandlerBlacklist extends ArgHandlerString {
+  protected static class ArgHandlerBlacklist extends ArgHandlerString {
     @Override
     public String getPurpose() {
       return "Prevents the user browsing URLs that match the specified regexes (comma or space separated)";
@@ -93,7 +94,13 @@
   /**
    * Handles the -noserver command line flag.
    */
-  protected class ArgHandlerNoServerFlag extends ArgHandlerFlag {
+  protected static class ArgHandlerNoServerFlag extends ArgHandlerFlag {
+    private final OptionNoServer options;
+
+    public ArgHandlerNoServerFlag(OptionNoServer options) {
+      this.options = options;
+    }
+
     @Override
     public String getPurpose() {
       return "Prevents the embedded Tomcat server from running, even if a port is specified";
@@ -106,7 +113,7 @@
 
     @Override
     public boolean setFlag() {
-      runTomcat = false;
+      options.setNoServer(true);
       return true;
     }
   }
@@ -114,7 +121,13 @@
   /**
    * Handles the -port command line flag.
    */
-  protected class ArgHandlerPort extends ArgHandlerString {
+  protected static class ArgHandlerPort extends ArgHandlerString {
+
+    private final OptionPort options;
+
+    public ArgHandlerPort(OptionPort options) {
+      this.options = options;
+    }
 
     @Override
     public String[] getDefaultArgs() {
@@ -139,10 +152,10 @@
     @Override
     public boolean setString(String value) {
       if (value.equals("auto")) {
-        port = 0;
+        options.setPort(0);
       } else {
         try {
-          port = Integer.parseInt(value);
+          options.setPort(Integer.parseInt(value));
         } catch (NumberFormatException e) {
           System.err.println("A port must be an integer or \"auto\"");
           return false;
@@ -155,7 +168,7 @@
   /**
    * Handles the -whitelist command line flag.
    */
-  protected class ArgHandlerWhitelist extends ArgHandlerString {
+  protected static class ArgHandlerWhitelist extends ArgHandlerString {
     @Override
     public String getPurpose() {
       return "Allows the user to browse URLs that match the specified regexes (comma or space separated)";
@@ -177,26 +190,8 @@
     }
   }
 
-  abstract class ArgProcessor extends ArgProcessorBase {
-    public ArgProcessor() {
-      registerHandler(getArgHandlerPort());
-      registerHandler(new ArgHandlerWhitelist());
-      registerHandler(new ArgHandlerBlacklist());
-      registerHandler(new ArgHandlerLogLevel(options) {
-        @Override
-        protected Type getDefaultLogLevel() {
-          return doGetDefaultLogLevel();
-        }
-      });
-      registerHandler(new ArgHandlerGenDir(options));
-      registerHandler(new ArgHandlerScriptStyle(options));
-      registerHandler(new ArgHandlerEnableAssertions(options));
-      registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
-    }
-  }
-
-  interface HostedModeBaseOptions extends JJSOptions, OptionLogLevel,
-      OptionGenDir {
+  protected interface HostedModeBaseOptions extends JJSOptions, OptionLogLevel,
+      OptionGenDir, OptionNoServer, OptionPort, OptionStartupURLs {
 
     /**
      * The base shell work directory.
@@ -204,6 +199,90 @@
     File getShellBaseWorkDir(ModuleDef moduleDef);
   }
 
+  /**
+   * Concrete class to implement all hosted mode base options.
+   */
+  protected static class HostedModeBaseOptionsImpl extends
+      PrecompileOptionsImpl implements HostedModeBaseOptions {
+
+    private boolean isNoServer;
+    private int port;
+    private final List<String> startupURLs = new ArrayList<String>();
+
+    public void addStartupURL(String url) {
+      startupURLs.add(url);
+    }
+
+    public int getPort() {
+      return port;
+    }
+
+    public File getShellBaseWorkDir(ModuleDef moduleDef) {
+      return new File(new File(getWorkDir(), moduleDef.getName()), "shell");
+    }
+
+    public List<String> getStartupURLs() {
+      return Collections.unmodifiableList(startupURLs);
+    }
+
+    public boolean isNoServer() {
+      return isNoServer;
+    }
+
+    public void setNoServer(boolean isNoServer) {
+      this.isNoServer = isNoServer;
+    }
+
+    public void setPort(int port) {
+      this.port = port;
+    }
+  }
+
+  /**
+   * Controls whether to run a server or not.
+   * 
+   */
+  protected interface OptionNoServer {
+    boolean isNoServer();
+
+    void setNoServer(boolean isNoServer);
+  }
+
+  /**
+   * Controls what port to use.
+   * 
+   */
+  protected interface OptionPort {
+    int getPort();
+
+    void setPort(int port);
+  }
+
+  /**
+   * Controls the startup URLs.
+   */
+  protected interface OptionStartupURLs {
+    void addStartupURL(String url);
+
+    List<String> getStartupURLs();
+  }
+
+  abstract static class ArgProcessor extends ArgProcessorBase {
+    public ArgProcessor(HostedModeBaseOptions options, boolean forceServer) {
+      if (!forceServer) {
+        registerHandler(new ArgHandlerNoServerFlag(options));
+      }
+      registerHandler(new ArgHandlerPort(options));
+      registerHandler(new ArgHandlerWhitelist());
+      registerHandler(new ArgHandlerBlacklist());
+      registerHandler(new ArgHandlerLogLevel(options));
+      registerHandler(new ArgHandlerGenDir(options));
+      registerHandler(new ArgHandlerScriptStyle(options));
+      registerHandler(new ArgHandlerEnableAssertions(options));
+      registerHandler(new ArgHandlerDisableAggressiveOptimization(options));
+    }
+  }
+
   private class BrowserWidgetHostImpl implements BrowserWidgetHost {
 
     public void compile() throws UnableToCompleteException {
@@ -258,7 +337,7 @@
       return HostedModeBase.this.initModule(moduleName);
     }
 
-    @SuppressWarnings("deprecation")
+    @Deprecated
     public boolean isLegacyMode() {
       return HostedModeBase.this instanceof GWTShell;
     }
@@ -305,14 +384,8 @@
 
   private ShellMainWindow mainWnd;
 
-  private int port;
-
-  private boolean runTomcat = true;
-
   private boolean started;
 
-  private final List<String> startupUrls = new ArrayList<String>();
-
   public HostedModeBase() {
     // Set any platform specific system properties.
     BootStrapPlatform.init();
@@ -321,7 +394,7 @@
   }
 
   public final void addStartupURL(String url) {
-    startupUrls.add(url);
+    options.addStartupURL(url);
   }
 
   public final void closeAllBrowserWindows() {
@@ -331,7 +404,7 @@
   }
 
   public final int getPort() {
-    return port;
+    return options.getPort();
   }
 
   public TreeLogger getTopLogger() {
@@ -353,7 +426,7 @@
     // Launch a browser window for each startup url.
     String startupURL = "";
     try {
-      for (String prenormalized : startupUrls) {
+      for (String prenormalized : options.getStartupURLs()) {
         startupURL = normalizeURL(prenormalized);
         logger.log(TreeLogger.TRACE, "Starting URL: " + startupURL, null);
         BrowserWidget bw = openNewBrowserWindow();
@@ -448,11 +521,11 @@
   }
 
   public final void setPort(int port) {
-    this.port = port;
+    options.setPort(port);
   }
 
   public final void setRunTomcat(boolean run) {
-    runTomcat = run;
+    options.setNoServer(!run);
   }
 
   /**
@@ -493,14 +566,6 @@
   }
 
   /**
-   * Can be override to change the default log level in subclasses. JUnit does
-   * this for example.
-   */
-  protected Type doGetDefaultLogLevel() {
-    return Type.INFO;
-  }
-
-  /**
    * Derived classes can override to prevent automatic update checking.
    */
   protected boolean doShouldCheckForUpdates() {
@@ -523,13 +588,6 @@
 
   protected abstract int doStartUpServer();
 
-  /**
-   * Derived classes can override to set a default port.
-   */
-  protected ArgHandlerPort getArgHandlerPort() {
-    return new ArgHandlerPort();
-  }
-
   protected final BrowserWidgetHost getBrowserHost() {
     return browserHost;
   }
@@ -612,7 +670,7 @@
   }
 
   protected final void shutDown() {
-    if (!runTomcat) {
+    if (options.isNoServer()) {
       return;
     }
     doShutDownServer();
@@ -633,12 +691,12 @@
       return false;
     }
 
-    if (runTomcat) {
+    if (!options.isNoServer()) {
       int resultPort = doStartUpServer();
       if (resultPort < 0) {
         return false;
       }
-      port = resultPort;
+      options.setPort(resultPort);
     }
 
     return true;
@@ -690,8 +748,8 @@
 
     boolean checkForUpdates = doShouldCheckForUpdates();
 
-    mainWnd = new ShellMainWindow(this, shell, runTomcat ? getPort() : 0,
-        checkForUpdates);
+    mainWnd = new ShellMainWindow(this, shell, options.isNoServer() ? 0
+        : getPort(), checkForUpdates);
 
     shell.setSize(700, 600);
     if (!isHeadless()) {
diff --git a/dev/core/test/com/google/gwt/dev/ArgProcessorTestBase.java b/dev/core/test/com/google/gwt/dev/ArgProcessorTestBase.java
new file mode 100644
index 0000000..5faff0d
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/ArgProcessorTestBase.java
@@ -0,0 +1,76 @@
+package com.google.gwt.dev;
+
+import com.google.gwt.util.tools.Utility;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Base class for argument processor testing.
+ */
+public abstract class ArgProcessorTestBase extends TestCase {
+
+  private static class MockOutputStream extends OutputStream {
+    private boolean isEmpty = true;
+
+    public boolean isEmpty() {
+      return isEmpty;
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+      isEmpty = false;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+      isEmpty = false;
+    }
+  }
+
+  /*
+   * The "compute installation directory" dance.
+   */
+  static {
+    String oldValue = System.getProperty("gwt.devjar");
+    System.setProperty("gwt.devjar", "gwt-dev-windows.jar");
+    Utility.getInstallPath();
+    if (oldValue == null) {
+      System.getProperties().remove("gwt.devjar");
+    } else {
+      System.setProperty("gwt.devjar", oldValue);
+    }
+  }
+
+  protected static void assertProcessFailure(ArgProcessorBase argProcessor,
+      String... args) {
+    PrintStream oldErrStream = System.err;
+    MockOutputStream myErrStream = new MockOutputStream();
+    try {
+      System.setErr(new PrintStream(myErrStream, true));
+      assertFalse(argProcessor.processArgs(args));
+    } finally {
+      System.setErr(oldErrStream);
+    }
+    assertFalse(myErrStream.isEmpty());
+  }
+
+  protected static void assertProcessSuccess(ArgProcessorBase argProcessor,
+      String... args) {
+    PrintStream oldErrStream = System.err;
+    ByteArrayOutputStream myErrStream = new ByteArrayOutputStream();
+    try {
+      System.setErr(new PrintStream(myErrStream, true));
+      if (!argProcessor.processArgs(args)) {
+        fail(new String(myErrStream.toByteArray()));
+      }
+      assertEquals(0, myErrStream.size());
+    } finally {
+      System.setErr(oldErrStream);
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/CompilerTest.java b/dev/core/test/com/google/gwt/dev/CompilerTest.java
new file mode 100644
index 0000000..cda54e3
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/CompilerTest.java
@@ -0,0 +1,68 @@
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.Compiler.CompilerOptionsImpl;
+import com.google.gwt.dev.jjs.JsOutputOption;
+
+import java.io.File;
+
+/**
+ * Test for {@link Compiler}.
+ */
+public class CompilerTest extends ArgProcessorTestBase {
+
+  private final Compiler.ArgProcessor argProcessor;
+  private final CompilerOptionsImpl options = new CompilerOptionsImpl();
+
+  public CompilerTest() {
+    argProcessor = new Compiler.ArgProcessor(options);
+  }
+
+  public void testAllValidArgs() {
+    assertProcessSuccess(argProcessor, "-logLevel", "DEBUG", "-style",
+        "PRETTY", "-ea", "-XdisableAggressiveOptimization", "-gen", "myGen",
+        "-war", "myWar", "-workDir", "myWork", "-extra", "myExtra",
+        "-localWorkers", "2", "c.g.g.h.H", "my.Module");
+
+    assertEquals(new File("myGen").getAbsoluteFile(),
+        options.getGenDir().getAbsoluteFile());
+    assertEquals(new File("myWar"), options.getWarDir());
+    assertEquals(new File("myWork"), options.getWorkDir());
+    assertEquals(new File("myExtra"), options.getExtraDir());
+
+    assertEquals(2, options.getLocalWorkers());
+
+    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+    assertEquals(JsOutputOption.PRETTY, options.getOutput());
+    assertTrue(options.isEnableAssertions());
+    assertFalse(options.isAggressivelyOptimize());
+
+    assertEquals(2, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+    assertEquals("my.Module", options.getModuleNames().get(1));
+  }
+
+  public void testDefaultArgs() {
+    assertProcessSuccess(argProcessor, "c.g.g.h.H");
+
+    assertEquals(null, options.getGenDir());
+    assertEquals(new File("war").getAbsoluteFile(),
+        options.getWarDir().getAbsoluteFile());
+    assertEquals(null, options.getWorkDir());
+    assertEquals(null, options.getExtraDir());
+
+    assertEquals(TreeLogger.INFO, options.getLogLevel());
+    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+    assertFalse(options.isEnableAssertions());
+    assertTrue(options.isAggressivelyOptimize());
+
+    assertEquals(1, options.getLocalWorkers());
+
+    assertEquals(1, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+  }
+
+  public void testForbiddenArgs() {
+    assertProcessFailure(argProcessor, "-out", "www");
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java b/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java
new file mode 100644
index 0000000..fb4e07b
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GWTCompilerTest.java
@@ -0,0 +1,63 @@
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.GWTCompiler.GWTCompilerOptionsImpl;
+import com.google.gwt.dev.jjs.JsOutputOption;
+
+import java.io.File;
+
+/**
+ * Test for deprecated {@link GWTShell}.
+ */
+@SuppressWarnings("deprecation")
+public class GWTCompilerTest extends ArgProcessorTestBase {
+
+  private final GWTCompiler.ArgProcessor argProcessor;
+  private final GWTCompilerOptionsImpl options = new GWTCompilerOptionsImpl();
+
+  public GWTCompilerTest() {
+    argProcessor = new GWTCompiler.ArgProcessor(options);
+  }
+
+  public void testAllValidArgs() {
+    assertProcessSuccess(argProcessor, "-logLevel", "DEBUG", "-style",
+        "PRETTY", "-ea", "-XdisableAggressiveOptimization", "-out", "myWww",
+        "-gen", "myGen", "c.g.g.h.H", "my.Module");
+
+    assertEquals(new File("myGen").getAbsoluteFile(),
+        options.getGenDir().getAbsoluteFile());
+    assertEquals(new File("myWww"), options.getOutDir());
+
+    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+    assertEquals(JsOutputOption.PRETTY, options.getOutput());
+    assertTrue(options.isEnableAssertions());
+    assertFalse(options.isAggressivelyOptimize());
+
+    assertEquals(2, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+    assertEquals("my.Module", options.getModuleNames().get(1));
+  }
+
+  public void testDefaultArgs() {
+    assertProcessSuccess(argProcessor, "c.g.g.h.H");
+
+    assertEquals(null, options.getGenDir());
+    assertEquals(new File("").getAbsoluteFile(),
+        options.getOutDir().getAbsoluteFile());
+
+    assertEquals(TreeLogger.INFO, options.getLogLevel());
+    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+    assertFalse(options.isEnableAssertions());
+    assertTrue(options.isAggressivelyOptimize());
+
+    assertEquals(1, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+  }
+
+  public void testForbiddenArgs() {
+    assertProcessFailure(argProcessor, "-localWorkers", "2");
+    assertProcessFailure(argProcessor, "-extra", "extra");
+    assertProcessFailure(argProcessor, "-war", "war");
+    assertProcessFailure(argProcessor, "-work", "work");
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/GWTShellTest.java b/dev/core/test/com/google/gwt/dev/GWTShellTest.java
new file mode 100644
index 0000000..7b76e59
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/GWTShellTest.java
@@ -0,0 +1,73 @@
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.GWTShell.ShellOptionsImpl;
+import com.google.gwt.dev.HostedModeTest.MySCL;
+import com.google.gwt.dev.jjs.JsOutputOption;
+import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
+
+import java.io.File;
+
+/**
+ * Test for deprecated {@link GWTShell}.
+ */
+@SuppressWarnings("deprecation")
+public class GWTShellTest extends ArgProcessorTestBase {
+
+  private final GWTShell.ArgProcessor argProcessor;
+  private final ShellOptionsImpl options = new ShellOptionsImpl();
+
+  public GWTShellTest() {
+    argProcessor = new GWTShell.ArgProcessor(options, false, false);
+  }
+
+  public void testAllValidArgs() {
+    assertProcessSuccess(argProcessor, "-port", "8080", "-whitelist", "white",
+        "-blacklist", "black", "-logLevel", "DEBUG", "-style", "PRETTY", "-ea",
+        "-XdisableAggressiveOptimization", "-noserver", "-out", "myWww",
+        "-gen", "myGen", "http://www.google.com/", "foo");
+
+    assertNotNull(BrowserWidgetHostChecker.matchWhitelisted("white"));
+    assertNotNull(BrowserWidgetHostChecker.matchBlacklisted("black"));
+
+    assertEquals(new File("myGen").getAbsoluteFile(),
+        options.getGenDir().getAbsoluteFile());
+    assertEquals(new File("myWww"), options.getOutDir());
+
+    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+    assertEquals(JsOutputOption.PRETTY, options.getOutput());
+    assertTrue(options.isEnableAssertions());
+    assertFalse(options.isAggressivelyOptimize());
+
+    assertEquals(8080, options.getPort());
+    assertTrue(options.isNoServer());
+    assertEquals(2, options.getStartupURLs().size());
+    assertEquals("http://www.google.com/", options.getStartupURLs().get(0));
+    assertEquals("foo", options.getStartupURLs().get(1));
+  }
+
+  public void testDefaultArgs() {
+    assertProcessSuccess(argProcessor);
+
+    assertEquals(null, options.getGenDir());
+    assertEquals(new File("").getAbsoluteFile(),
+        options.getOutDir().getAbsoluteFile());
+
+    assertEquals(TreeLogger.INFO, options.getLogLevel());
+    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+    assertFalse(options.isEnableAssertions());
+    assertTrue(options.isAggressivelyOptimize());
+
+    assertEquals(8888, options.getPort());
+    assertFalse(options.isNoServer());
+    assertEquals(0, options.getStartupURLs().size());
+  }
+
+  public void testForbiddenArgs() {
+    assertProcessFailure(argProcessor, "-localWorkers", "2");
+    assertProcessFailure(argProcessor, "-extra", "extra");
+    assertProcessFailure(argProcessor, "-war", "war");
+    assertProcessFailure(argProcessor, "-work", "work");
+    assertProcessFailure(argProcessor, "-server", MySCL.class.getName());
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/HostedModeTest.java b/dev/core/test/com/google/gwt/dev/HostedModeTest.java
new file mode 100644
index 0000000..015e943
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/HostedModeTest.java
@@ -0,0 +1,121 @@
+package com.google.gwt.dev;
+
+import com.google.gwt.core.ext.ServletContainer;
+import com.google.gwt.core.ext.ServletContainerLauncher;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.HostedMode.HostedModeOptionsImpl;
+import com.google.gwt.dev.jjs.JsOutputOption;
+import com.google.gwt.dev.shell.BrowserWidgetHostChecker;
+
+import java.io.File;
+import java.net.BindException;
+
+/**
+ * Test for {@link HostedMode}.
+ */
+public class HostedModeTest extends ArgProcessorTestBase {
+
+  public static class MySCL extends ServletContainerLauncher {
+    public ServletContainer start(TreeLogger logger, int port, File appRootDir)
+        throws BindException, Exception {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private final HostedMode.ArgProcessor argProcessor;
+  private final HostedModeOptionsImpl options = new HostedModeOptionsImpl();
+
+  public HostedModeTest() {
+    argProcessor = new HostedMode.ArgProcessor(options);
+  }
+
+  public void testAllValidArgs() {
+    assertProcessSuccess(argProcessor, "-port", "8080", "-whitelist", "white",
+        "-blacklist", "black", "-logLevel", "DEBUG", "-style", "PRETTY", "-ea",
+        "-XdisableAggressiveOptimization", "-noserver", "-server",
+        MySCL.class.getName(), "-gen", "myGen", "-war", "myWar", "-workDir",
+        "myWork", "-extra", "myExtra", "-localWorkers", "2", "-startupUrl",
+        "http://www.google.com/", "-startupUrl", "foo", "c.g.g.h.H",
+        "my.Module");
+
+    assertNotNull(BrowserWidgetHostChecker.matchWhitelisted("white"));
+    assertNotNull(BrowserWidgetHostChecker.matchBlacklisted("black"));
+
+    assertEquals(new File("myGen").getAbsoluteFile(),
+        options.getGenDir().getAbsoluteFile());
+    assertEquals(new File("myWar"), options.getWarDir());
+    assertEquals(new File("myWork"), options.getWorkDir());
+    assertEquals(new File("myExtra"), options.getExtraDir());
+
+    assertEquals(TreeLogger.DEBUG, options.getLogLevel());
+    assertEquals(JsOutputOption.PRETTY, options.getOutput());
+    assertTrue(options.isEnableAssertions());
+    assertFalse(options.isAggressivelyOptimize());
+
+    assertEquals(2, options.getLocalWorkers());
+
+    assertEquals(8080, options.getPort());
+    // False because -server overrides -noserver.
+    assertFalse(options.isNoServer());
+    assertSame(MySCL.class, options.getServletContainerLauncher().getClass());
+
+    assertEquals(2, options.getStartupURLs().size());
+    assertEquals("http://www.google.com/", options.getStartupURLs().get(0));
+    assertEquals("foo", options.getStartupURLs().get(1));
+
+    assertEquals(2, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+    assertEquals("my.Module", options.getModuleNames().get(1));
+  }
+
+  public void testNoServer() {
+    assertProcessSuccess(argProcessor, "-noserver", "c.g.g.h.H");
+
+    assertTrue(options.isNoServer());
+    assertNull(options.getServletContainerLauncher());
+
+    assertEquals(1, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+  }
+
+  public void testNoServerOverridesServer() {
+    assertProcessSuccess(argProcessor, "-server", MySCL.class.getName(),
+        "-noserver", "c.g.g.h.H");
+
+    assertTrue(options.isNoServer());
+    assertSame(MySCL.class, options.getServletContainerLauncher().getClass());
+
+    assertEquals(1, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+  }
+
+  public void testDefaultArgs() {
+    assertProcessSuccess(argProcessor, "c.g.g.h.H");
+
+    assertEquals(null, options.getGenDir());
+    assertEquals(new File("war").getAbsoluteFile(),
+        options.getWarDir().getAbsoluteFile());
+    assertEquals(null, options.getWorkDir());
+    assertEquals(null, options.getExtraDir());
+
+    assertEquals(TreeLogger.INFO, options.getLogLevel());
+    assertEquals(JsOutputOption.OBFUSCATED, options.getOutput());
+    assertFalse(options.isEnableAssertions());
+    assertTrue(options.isAggressivelyOptimize());
+
+    assertEquals(1, options.getLocalWorkers());
+
+    assertEquals(8888, options.getPort());
+    assertFalse(options.isNoServer());
+    assertNotNull(options.getServletContainerLauncher());
+
+    assertEquals(0, options.getStartupURLs().size());
+
+    assertEquals(1, options.getModuleNames().size());
+    assertEquals("c.g.g.h.H", options.getModuleNames().get(0));
+  }
+
+  public void testForbiddenArgs() {
+    assertProcessFailure(argProcessor, "-out", "www");
+  }
+}
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 3fc8e80..b913922 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -28,6 +28,7 @@
 import com.google.gwt.dev.cfg.Properties;
 import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.junit.client.TimeoutException;
 import com.google.gwt.junit.client.impl.GWTRunner;
@@ -76,6 +77,7 @@
  * {@link JUnitMessageQueue}, thus closing the loop.
  * </p>
  */
+@SuppressWarnings("deprecation")
 public class JUnitShell extends GWTShell {
 
   /**
@@ -92,9 +94,25 @@
   class ArgProcessor extends GWTShell.ArgProcessor {
 
     public ArgProcessor() {
-      super(true, true);
-      registerHandler(new ArgHandlerFlag() {
+      super(options, true, true);
 
+      // Override port to set auto by default.
+      registerHandler(new ArgHandlerPort(options) {
+        @Override
+        public String[] getDefaultArgs() {
+          return new String[] {"-port", "auto"};
+        }
+      });
+
+      // Override log level to set WARN by default..
+      registerHandler(new ArgHandlerLogLevel(options) {
+        @Override
+        protected Type getDefaultLogLevel() {
+          return TreeLogger.WARN;
+        }
+      });
+
+      registerHandler(new ArgHandlerFlag() {
         @Override
         public String getPurpose() {
           return "Causes your test to run in web (compiled) mode (defaults to hosted mode)";
@@ -111,11 +129,9 @@
           numClients = 1;
           return true;
         }
-
       });
 
       registerHandler(new ArgHandlerString() {
-
         @Override
         public String getPurpose() {
           return "Runs web mode via RMI to a set of BrowserManagerServers; "
@@ -147,7 +163,6 @@
       });
 
       registerHandler(new ArgHandlerString() {
-
         @Override
         public String getPurpose() {
           return "Runs web mode via HTTP to a set of Selenium servers; "
@@ -174,7 +189,6 @@
       });
 
       registerHandler(new ArgHandlerString() {
-
         @Override
         public String getPurpose() {
           return "Run external browsers in web mode (pass a comma separated list of executables.)";
@@ -205,7 +219,6 @@
       });
 
       registerHandler(new ArgHandler() {
-
         @Override
         public String[] getDefaultArgs() {
           return null;
@@ -249,7 +262,6 @@
           runStyle = new RunStyleManual(JUnitShell.this, value);
           numClients = value;
         }
-
       });
 
       registerHandler(new ArgHandlerFlag() {
@@ -485,11 +497,6 @@
     }
   }
 
-  @Override
-  protected Type doGetDefaultLogLevel() {
-    return Type.WARN;
-  }
-
   /**
    * Never check for updates in JUnit mode.
    */
@@ -499,16 +506,6 @@
   }
 
   @Override
-  protected ArgHandlerPort getArgHandlerPort() {
-    return new ArgHandlerPort() {
-      @Override
-      public String[] getDefaultArgs() {
-        return new String[] {"-port", "auto"};
-      }
-    };
-  }
-
-  @Override
   protected void initializeLogger() {
     if (isHeadless()) {
       consoleLogger = new PrintWriterTreeLogger();