aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zma...@apache.org
Subject [09/37] aurora git commit: Import of Twitter Commons.
Date Tue, 25 Aug 2015 18:19:23 GMT
http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/main/thrift/com/twitter/thrift/endpoint.thrift
----------------------------------------------------------------------
diff --git a/commons/src/main/thrift/com/twitter/thrift/endpoint.thrift b/commons/src/main/thrift/com/twitter/thrift/endpoint.thrift
new file mode 100644
index 0000000..2d14541
--- /dev/null
+++ b/commons/src/main/thrift/com/twitter/thrift/endpoint.thrift
@@ -0,0 +1,115 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or 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.
+// =================================================================================================
+
+// Author: jsirois
+
+// TODO(wickman)  Fix uses of this in python+science!  Especially expertsearch
+//  This should be aliased against twitter.thrift or twitter.common.service, or
+//  wherever the python service discovery stack lands.
+
+namespace java com.twitter.thrift
+#@namespace scala com.twitter.thrift.endpoint.thriftscala
+namespace rb Twitter.Thrift
+namespace py gen.twitter.thrift.endpoint
+
+/*
+ * Represents the status of a service.
+ */
+enum Status {
+
+  /*
+   * The service is dead and can no longer be contacted.
+   */
+  DEAD = 0,
+
+  /*
+   * The service is in the process of starting up for the first time or from a STOPPED state.
+   */
+  STARTING = 1,
+
+  /*
+   * The service is alive and ready to receive requests.
+   */
+  ALIVE = 2,
+
+  /*
+   * The service is in the process of stopping and should no longer be contacted.  In this state
+   * well behaved services will typically finish existing requests but accept no new rtequests.
+   */
+  STOPPING = 3,
+
+  /*
+   * The service is stopped and cannot be contacted unless started again.
+   */
+  STOPPED = 4,
+
+  /*
+   * The service is alive but in a potentially bad state.
+   */
+  WARNING = 5,
+}
+
+/*
+ * Represents a TCP service network endpoint.
+ */
+struct Endpoint {
+
+  /*
+   * The remote hostname or ip address of the endpoint.
+   */
+  1: string host
+
+  /*
+   * The TCP port the endpoint listens on.
+   */
+  2: i32 port
+}
+
+/*
+ * Represents information about the state of a service instance.
+ */
+struct ServiceInstance {
+
+  /*
+   * Represents the primary service interface endpoint.  This is typically a thrift service
+   * endpoint.
+   */
+  1: Endpoint serviceEndpoint
+
+  /*
+   * A mapping of any additional interfaces the service exports.  The mapping is from logical
+   * interface names to endpoints.  The map may be empty, but a typical additional endpoint mapping
+   * would provide the endoint got the "http-admin" debug interface for example.
+   *
+   * TODO(John Sirois): consider promoting string -> Enum or adding thrift string constants for common
+   * service names to help identify common beasts like ostrich-http-admin, ostrich-telnet and
+   * process-http-admin but still allow for new experimental interfaces as well without having to
+   * change this thift file.
+   */
+  2: map<string, Endpoint> additionalEndpoints
+
+  /*
+   * The status of this service instance.
+   * NOTE: Only status ALIVE should be used. This field is pending removal.
+   * TODO(Sathya Hariesh): Remove the status field.
+   */
+  3: Status status;
+
+  /*
+   * The shard identifier for this instance.
+   */
+  4: optional i32 shard;
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/application/AppLauncherTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/application/AppLauncherTest.java b/commons/src/test/java/com/twitter/common/application/AppLauncherTest.java
new file mode 100644
index 0000000..b428f5e
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/application/AppLauncherTest.java
@@ -0,0 +1,77 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.application;
+
+import java.lang.reflect.Field;
+
+import com.google.common.base.Predicates;
+
+import org.junit.Test;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.ArgFilters;
+import com.twitter.common.args.CmdLine;
+import com.twitter.common.args.constraints.NotNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author John Sirois
+ */
+public class AppLauncherTest {
+
+  public static class TestApp1 extends AbstractApplication {
+    @NotNull
+    @CmdLine(name = "user", help = "a username")
+    static final Arg<String> USER = Arg.create();
+
+    private static boolean hasRun;
+
+    @Override public void run() {
+      hasRun = true;
+    }
+  }
+
+  @Test
+  public void testLaunch1() {
+    AppLauncher.launch(TestApp1.class, ArgFilters.selectClass(TestApp1.class), "-user", "jake");
+    assertTrue(TestApp1.hasRun);
+    assertEquals("jake", TestApp1.USER.get());
+  }
+
+  public static class TestApp2 extends AbstractApplication {
+    @NotNull
+    @CmdLine(name = "user", help = "a username")
+    static final Arg<String> USER = Arg.create(null);
+
+    private static boolean hasRun;
+
+    @Override public void run() {
+      hasRun = true;
+    }
+  }
+
+  @Test
+  public void testLaunch2() {
+    // We filter out the NotNull Arg so we should be able to launch without specifying it.
+    AppLauncher.launch(TestApp2.class, Predicates.<Field>alwaysFalse());
+    assertTrue(TestApp2.hasRun);
+    assertNull(TestApp2.USER.get());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/application/modules/LifecycleModuleTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/application/modules/LifecycleModuleTest.java b/commons/src/test/java/com/twitter/common/application/modules/LifecycleModuleTest.java
new file mode 100644
index 0000000..2d8d61e
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/application/modules/LifecycleModuleTest.java
@@ -0,0 +1,124 @@
+package com.twitter.common.application.modules;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.net.InetSocketAddress;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+import org.junit.Test;
+
+import com.twitter.common.application.ShutdownRegistry.ShutdownRegistryImpl;
+import com.twitter.common.application.modules.LifecycleModule.LaunchException;
+import com.twitter.common.application.modules.LifecycleModule.ServiceRunner;
+import com.twitter.common.application.modules.LocalServiceRegistry.LocalService;
+import com.twitter.common.base.Command;
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import static com.twitter.common.application.modules.LifecycleModule.bindLocalService;
+import static com.twitter.common.net.InetSocketAddressHelper.getLocalAddress;
+
+/**
+ * @author William Farner
+ */
+public class LifecycleModuleTest extends EasyMockTest {
+
+  private static class SystemModule extends AbstractModule {
+    @Override protected void configure() {
+      install(new LifecycleModule());
+      bind(UncaughtExceptionHandler.class).toInstance(new UncaughtExceptionHandler() {
+        @Override public void uncaughtException(Thread thread, Throwable throwable) {
+          fail("Uncaught exception.");
+        }
+      });
+    }
+  }
+
+  @Test
+  public void testNoServices() {
+    control.replay();
+
+    Injector injector = Guice.createInjector(new SystemModule());
+
+    LocalServiceRegistry registry = injector.getInstance(LocalServiceRegistry.class);
+    assertEquals(Optional.<InetSocketAddress>absent(), registry.getPrimarySocket());
+    assertEquals(ImmutableMap.<String, InetSocketAddress>of(), registry.getAuxiliarySockets());
+  }
+
+  @Test
+  public void testNoRunner() throws Exception {
+    final Command primaryShutdown = createMock(Command.class);
+    final Command auxShutdown = createMock(Command.class);
+
+    primaryShutdown.execute();
+    auxShutdown.execute();
+
+    Module testModule = new AbstractModule() {
+      @Override protected void configure() {
+        bindLocalService(binder(), LocalService.primaryService(99, primaryShutdown));
+        bindLocalService(binder(), LocalService.auxiliaryService("foo", 100, auxShutdown));
+      }
+    };
+
+    Injector injector = Guice.createInjector(new SystemModule(), testModule);
+    LocalServiceRegistry registry = injector.getInstance(LocalServiceRegistry.class);
+
+    control.replay();
+
+    assertEquals(Optional.of(getLocalAddress(99)), registry.getPrimarySocket());
+    assertEquals(ImmutableMap.of("foo", getLocalAddress(100)), registry.getAuxiliarySockets());
+
+    injector.getInstance(ShutdownRegistryImpl.class).execute();
+  }
+
+  @Test
+  public void testOrdering() throws Exception {
+    final ServiceRunner runner = createMock(ServiceRunner.class);
+    Command shutdown = createMock(Command.class);
+
+    expect(runner.launch()).andReturn(LocalService.primaryService(100, shutdown));
+    shutdown.execute();
+
+    Module testModule = new AbstractModule() {
+      @Override protected void configure() {
+        LifecycleModule.runnerBinder(binder()).addBinding().toInstance(runner);
+      }
+    };
+
+    Injector injector = Guice.createInjector(new SystemModule(), testModule);
+    LocalServiceRegistry registry = injector.getInstance(LocalServiceRegistry.class);
+
+    control.replay();
+
+    assertEquals(Optional.of(getLocalAddress(100)), registry.getPrimarySocket());
+    injector.getInstance(ShutdownRegistryImpl.class).execute();
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testFailedLauncher() throws Exception {
+    final ServiceRunner runner = createMock(ServiceRunner.class);
+
+    expect(runner.launch()).andThrow(new LaunchException("Injected failure."));
+
+    Module testModule = new AbstractModule() {
+      @Override protected void configure() {
+        LifecycleModule.runnerBinder(binder()).addBinding().toInstance(runner);
+      }
+    };
+
+    Injector injector = Guice.createInjector(new SystemModule(), testModule);
+    LocalServiceRegistry registry = injector.getInstance(LocalServiceRegistry.class);
+
+    control.replay();
+
+    assertEquals(Optional.of(getLocalAddress(100)), registry.getPrimarySocket());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/application/modules/LocalServiceRegistryTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/application/modules/LocalServiceRegistryTest.java b/commons/src/test/java/com/twitter/common/application/modules/LocalServiceRegistryTest.java
new file mode 100644
index 0000000..50acdf7
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/application/modules/LocalServiceRegistryTest.java
@@ -0,0 +1,157 @@
+package com.twitter.common.application.modules;
+
+import java.net.InetSocketAddress;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.inject.Provider;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.application.ShutdownRegistry;
+import com.twitter.common.application.modules.LifecycleModule.LaunchException;
+import com.twitter.common.application.modules.LifecycleModule.ServiceRunner;
+import com.twitter.common.application.modules.LocalServiceRegistry.LocalService;
+import com.twitter.common.base.Commands;
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author William Farner
+ */
+public class LocalServiceRegistryTest extends EasyMockTest {
+
+  private static final Function<InetSocketAddress, Integer> INET_TO_PORT =
+      new Function<InetSocketAddress, Integer>() {
+        @Override public Integer apply(InetSocketAddress address) {
+          return address.getPort();
+        }
+      };
+
+  private static final String A = "a";
+  private static final String B = "b";
+  private static final String C = "c";
+
+  private ServiceRunner runner1;
+  private ServiceRunner runner2;
+  private Provider<Set<ServiceRunner>> serviceProvider;
+  private ShutdownRegistry shutdownRegistry;
+  private LocalServiceRegistry registry;
+
+  @Before
+  public void setUp() {
+    runner1 = createMock(ServiceRunner.class);
+    runner2 = createMock(ServiceRunner.class);
+    serviceProvider = createMock(new Clazz<Provider<Set<ServiceRunner>>>() { });
+    shutdownRegistry = createMock(ShutdownRegistry.class);
+    registry = new LocalServiceRegistry(serviceProvider, shutdownRegistry);
+  }
+
+  @Test
+  public void testCreate() throws LaunchException {
+    expect(serviceProvider.get()).andReturn(ImmutableSet.of(runner1, runner2));
+    expect(runner1.launch()).andReturn(primary(1));
+    expect(runner2.launch()).andReturn(auxiliary(A, 2));
+    shutdownRegistry.addAction(Commands.NOOP);
+    expectLastCall().times(2);
+
+    control.replay();
+
+    checkPorts(Optional.of(1), ImmutableMap.of(A, 2));
+  }
+
+  private LocalService primary(int port) {
+    return LocalService.primaryService(port, Commands.NOOP);
+  }
+
+  private LocalService auxiliary(String name, int port) {
+    return LocalService.auxiliaryService(name, port, Commands.NOOP);
+  }
+
+  private LocalService auxiliary(Set<String> names, int port) {
+    return LocalService.auxiliaryService(names, port, Commands.NOOP);
+  }
+
+  @Test
+  public void testNoPrimary() throws LaunchException {
+    expect(serviceProvider.get()).andReturn(ImmutableSet.of(runner1));
+    expect(runner1.launch()).andReturn(auxiliary(A, 2));
+    shutdownRegistry.addAction(Commands.NOOP);
+    expectLastCall().times(1);
+
+    control.replay();
+
+    assertFalse(registry.getPrimarySocket().isPresent());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMultiplePrimaries() throws LaunchException {
+    expect(serviceProvider.get()).andReturn(ImmutableSet.of(runner1, runner2));
+    expect(runner1.launch()).andReturn(primary(1));
+    expect(runner2.launch()).andReturn(primary(2));
+    shutdownRegistry.addAction(Commands.NOOP);
+    expectLastCall().times(2);
+
+    control.replay();
+
+    registry.getPrimarySocket();
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testDuplicateName() throws LaunchException {
+    expect(serviceProvider.get()).andReturn(ImmutableSet.of(runner1, runner2));
+    expect(runner1.launch()).andReturn(auxiliary(A, 1));
+    expect(runner2.launch()).andReturn(auxiliary(A, 2));
+    shutdownRegistry.addAction(Commands.NOOP);
+    expectLastCall().times(2);
+
+    control.replay();
+
+    registry.getPrimarySocket();
+  }
+
+  @Test
+  public void testAllowsPortReuse() throws LaunchException {
+    expect(serviceProvider.get()).andReturn(ImmutableSet.of(runner1, runner2));
+    expect(runner1.launch()).andReturn(auxiliary(A, 2));
+    expect(runner2.launch()).andReturn(auxiliary(B, 2));
+    shutdownRegistry.addAction(Commands.NOOP);
+    expectLastCall().times(2);
+
+    control.replay();
+
+    checkPorts(Optional.<Integer>absent(), ImmutableMap.of(A, 2, B, 2));
+  }
+
+  @Test
+  public void testMultiNameBreakout() throws LaunchException {
+    expect(serviceProvider.get()).andReturn(ImmutableSet.of(runner1, runner2));
+    expect(runner1.launch()).andReturn(auxiliary(A, 2));
+    expect(runner2.launch()).andReturn(auxiliary(ImmutableSet.of(B, C), 6));
+    shutdownRegistry.addAction(Commands.NOOP);
+    expectLastCall().times(2);
+
+    control.replay();
+
+    checkPorts(Optional.<Integer>absent(), ImmutableMap.of(A, 2, B, 6, C, 6));
+  }
+
+  private void checkPorts(Optional<Integer> primary, Map<String, Integer> expected) {
+    Optional<InetSocketAddress> registeredSocket = registry.getPrimarySocket();
+    Optional<Integer> registeredPort = registeredSocket.isPresent()
+        ? Optional.of(registeredSocket.get().getPort()) : Optional.<Integer>absent();
+
+    assertEquals(primary, registeredPort);
+    assertEquals(expected, Maps.transformValues(registry.getAuxiliarySockets(), INET_TO_PORT));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/application/modules/StartStatPollerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/application/modules/StartStatPollerTest.java b/commons/src/test/java/com/twitter/common/application/modules/StartStatPollerTest.java
new file mode 100644
index 0000000..5def8a8
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/application/modules/StartStatPollerTest.java
@@ -0,0 +1,50 @@
+package com.twitter.common.application.modules;
+
+import java.util.Properties;
+
+import org.junit.Test;
+
+import com.twitter.common.application.ShutdownRegistry;
+import com.twitter.common.stats.Stat;
+import com.twitter.common.stats.Stats;
+import com.twitter.common.stats.TimeSeriesRepository;
+import com.twitter.common.testing.easymock.EasyMockTest;
+import com.twitter.common.util.BuildInfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class StartStatPollerTest extends EasyMockTest {
+  @Test
+  public void testStartStatPollerExecute() {
+    ShutdownRegistry shutdownRegistry = createMock(ShutdownRegistry.class);
+    TimeSeriesRepository repository = createMock(TimeSeriesRepository.class);
+
+    Properties properties = new Properties();
+    final Long gitRevisionNumber = 1404461016779713L;
+    properties.setProperty(BuildInfo.Key.GIT_REVISION_NUMBER.value, gitRevisionNumber.toString());
+    String gitRevision = "foo_branch";
+    properties.setProperty(BuildInfo.Key.GIT_REVISION.value, gitRevision);
+    BuildInfo buildInfo = new BuildInfo(properties);
+
+    StatsModule.StartStatPoller poller =
+        new StatsModule.StartStatPoller(shutdownRegistry, buildInfo, repository);
+
+    repository.start(shutdownRegistry);
+    control.replay();
+
+    poller.execute();
+
+    Stat<Long> gitRevisionNumberStat =
+        Stats.getVariable(Stats.normalizeName(BuildInfo.Key.GIT_REVISION_NUMBER.value));
+    assertEquals(gitRevisionNumber, gitRevisionNumberStat.read());
+
+    Stat<String> gitRevisionStat =
+        Stats.getVariable(Stats.normalizeName(BuildInfo.Key.GIT_REVISION.value));
+    assertEquals(gitRevision, gitRevisionStat.read());
+
+    Stat<String> gitBranchNameStat =
+        Stats.getVariable(Stats.normalizeName(BuildInfo.Key.GIT_BRANCHNAME.value));
+    assertNull(gitBranchNameStat);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/ArgFiltersTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/ArgFiltersTest.java b/commons/src/test/java/com/twitter/common/args/ArgFiltersTest.java
new file mode 100644
index 0000000..6dc8ad5
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/ArgFiltersTest.java
@@ -0,0 +1,108 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+import org.junit.Test;
+
+import com.twitter.common.args.apt.Configuration;
+import com.twitter.common.args.argfilterstest.ArgsRoot;
+import com.twitter.common.args.argfilterstest.subpackageA.ArgsA;
+import com.twitter.common.args.argfilterstest.subpackageA.subsubpackage1.ArgsA1;
+import com.twitter.common.args.argfilterstest.subpackageB.ArgsB;
+import com.twitter.common.args.argfilterstest.subpackageBwithSuffix.ArgsBWithSuffix;
+
+import static org.junit.Assert.assertEquals;
+
+import static com.twitter.common.args.apt.Configuration.ArgInfo;
+
+/**
+ * @author John Sirois
+ */
+public class ArgFiltersTest {
+
+  @CmdLine(name = "a", help = "")
+  static final Arg<String> A = Arg.create();
+
+  @CmdLine(name = "b", help = "")
+  static final Arg<String> B = Arg.create();
+
+  @Test
+  public void testpackage() throws IOException {
+    testFilter(ArgFilters.selectPackage(ArgsRoot.class.getPackage()),
+        fieldInfo(ArgsRoot.class, "ARGS_ROOT"));
+  }
+
+  @Test
+  public void testAllPackagesUnderHere() throws IOException {
+    testFilter(ArgFilters.selectAllPackagesUnderHere(ArgsRoot.class.getPackage()),
+        fieldInfo(ArgsRoot.class, "ARGS_ROOT"),
+        fieldInfo(ArgsA.class, "ARGS_A"),
+        fieldInfo(ArgsB.class, "ARGS_B"),
+        fieldInfo(ArgsA1.class, "ARGS_A1"),
+        fieldInfo(ArgsBWithSuffix.class, "ARGS_B_WITH_SUFFIX"));
+
+    testFilter(ArgFilters.selectAllPackagesUnderHere(ArgsB.class.getPackage()),
+        fieldInfo(ArgsB.class, "ARGS_B"));
+  }
+
+  @Test
+  public void testClass() throws IOException {
+    testFilter(ArgFilters.selectClass(ArgFiltersTest.class),
+        fieldInfo(ArgFiltersTest.class, "A"), fieldInfo(ArgFiltersTest.class, "B"));
+  }
+
+  @Test
+  public void testClasses() throws IOException {
+    testFilter(ArgFilters.selectClasses(ArgFiltersTest.class, ArgsA.class),
+        fieldInfo(ArgFiltersTest.class, "A"),
+        fieldInfo(ArgFiltersTest.class, "B"),
+        fieldInfo(ArgsA.class, "ARGS_A"));
+  }
+
+  @Test
+  public void testArg() throws IOException {
+    testFilter(ArgFilters.selectCmdLineArg(ArgFiltersTest.class, "b"),
+        fieldInfo(ArgFiltersTest.class, "B"));
+  }
+
+  private static ArgInfo fieldInfo(Class<?> declaringClass, String fieldName) {
+    return new ArgInfo(declaringClass.getName(), fieldName);
+  }
+
+  private void testFilter(final Predicate<Field> filter, ArgInfo... expected)
+      throws IOException {
+
+    Predicate<Optional<Field>> fieldFilter = new Predicate<Optional<Field>>() {
+      @Override public boolean apply(Optional<Field> maybeField) {
+        return maybeField.isPresent() && filter.apply(maybeField.get());
+      }
+    };
+
+    assertEquals(ImmutableSet.copyOf(expected),
+        ImmutableSet.copyOf(Iterables.filter(Configuration.load().optionInfo(),
+            Predicates.compose(fieldFilter, Args.TO_FIELD))));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/ArgScannerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/ArgScannerTest.java b/commons/src/test/java/com/twitter/common/args/ArgScannerTest.java
new file mode 100644
index 0000000..3a47f56
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/ArgScannerTest.java
@@ -0,0 +1,851 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteStreams;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.args.ArgScannerTest.StandardArgs.Optimizations;
+import com.twitter.common.args.constraints.NotEmpty;
+import com.twitter.common.args.constraints.NotNegative;
+import com.twitter.common.args.constraints.NotNull;
+import com.twitter.common.args.constraints.Positive;
+import com.twitter.common.args.constraints.Range;
+import com.twitter.common.args.parsers.NonParameterizedTypeParser;
+import com.twitter.common.base.Command;
+import com.twitter.common.base.Function;
+import com.twitter.common.base.MorePreconditions;
+import com.twitter.common.collections.Pair;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Data;
+import com.twitter.common.quantity.Time;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author William Farner
+ */
+public class ArgScannerTest {
+
+  private static final Function<Class<?>, Predicate<Field>> TO_SCOPE_PREDICATE =
+      new Function<Class<?>, Predicate<Field>>() {
+        @Override public Predicate<Field> apply(final Class<?> cls) {
+          return new Predicate<Field>() {
+            @Override public boolean apply(Field field) {
+              return field.getDeclaringClass() == cls;
+            }
+          };
+        }
+      };
+
+  @Before
+  public void setUp() {
+    // Reset args in all classes before each test.
+    for (Class<?> cls : this.getClass().getDeclaredClasses()) {
+      resetArgs(cls);
+    }
+  }
+
+  public static class StandardArgs {
+    enum Optimizations { NONE, MINIMAL, ALL }
+    @CmdLine(name = "enum", help = "help")
+    static final Arg<Optimizations> ENUM_VAL = Arg.create(Optimizations.MINIMAL);
+    @CmdLine(name = "string", help = "help")
+    static final Arg<String> STRING_VAL = Arg.create("string");
+    @CmdLine(name = "char", help = "help")
+    static final Arg<Character> CHAR_VAL = Arg.create('c');
+    @CmdLine(name = "byte", help = "help")
+    static final Arg<Byte> BYTE_VAL = Arg.create((byte) 0);
+    @CmdLine(name = "short", help = "help")
+    static final Arg<Short> SHORT_VAL = Arg.create((short) 0);
+    @CmdLine(name = "int", help = "help")
+    static final Arg<Integer> INT_VAL = Arg.create(0);
+    @CmdLine(name = "long", help = "help")
+    static final Arg<Long> LONG_VAL = Arg.create(0L);
+    @CmdLine(name = "float", help = "help")
+    static final Arg<Float> FLOAT_VAL = Arg.create(0F);
+    @CmdLine(name = "double", help = "help")
+    static final Arg<Double> DOUBLE_VAL = Arg.create(0D);
+    @CmdLine(name = "bool", help = "help")
+    static final Arg<Boolean> BOOL = Arg.create(false);
+    @CmdLine(name = "regex", help = "help")
+    static final Arg<Pattern> REGEX = Arg.create(null);
+    @CmdLine(name = "time_amount", help = "help")
+    static final Arg<Amount<Long, Time>> TIME_AMOUNT = Arg.create(Amount.of(1L, Time.SECONDS));
+    @CmdLine(name = "data_amount", help = "help")
+    static final Arg<Amount<Long, Data>> DATA_AMOUNT = Arg.create(Amount.of(1L, Data.MB));
+    @CmdLine(name = "range", help = "help")
+    static final Arg<com.google.common.collect.Range<Integer>> RANGE =
+        Arg.create(com.google.common.collect.Range.closed(1, 5));
+    @Positional(help = "help")
+    static final Arg<List<Amount<Long, Time>>> POSITIONAL =
+        Arg.<List<Amount<Long, Time>>>create(ImmutableList.<Amount<Long, Time>>of());
+  }
+
+  @Test
+  public void testStandardArgs() {
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.ENUM_VAL.get(), is(Optimizations.ALL));
+          }
+        }, "enum", "ALL");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.STRING_VAL.get(), is("newstring"));
+          }
+        },
+        "string", "newstring");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.CHAR_VAL.get(), is('x')); }
+        },
+        "char", "x");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.BYTE_VAL.get(), is((byte) 10));
+          }
+        },
+        "byte", "10");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.SHORT_VAL.get(), is((short) 10));
+          }
+        },
+        "short", "10");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.INT_VAL.get(), is(10)); }
+        },
+        "int", "10");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.LONG_VAL.get(), is(10L)); }
+        },
+        "long", "10");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.FLOAT_VAL.get(), is(10f)); }
+        },
+        "float", "10.0");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.DOUBLE_VAL.get(), is(10d)); }
+        },
+        "double", "10.0");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.BOOL.get(), is(true)); }
+        },
+        "bool", "true");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.BOOL.get(), is(true)); }
+        },
+        "bool", "");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.REGEX.get().matcher("jack").matches(), is(true));
+          }
+        },
+        "regex", ".*ack$");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.BOOL.get(), is(false)); }
+        },
+        "no_bool", "");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() { assertThat(StandardArgs.BOOL.get(), is(true)); }
+        },
+        "no_bool", "false");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.TIME_AMOUNT.get(), is(Amount.of(100L, Time.SECONDS)));
+          }
+        },
+        "time_amount", "100secs");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.DATA_AMOUNT.get(), is(Amount.of(1L, Data.Gb)));
+          }
+        },
+        "data_amount", "1Gb");
+    test(StandardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(StandardArgs.RANGE.get(), is(com.google.common.collect.Range.closed(1, 5)));
+          }
+        },
+        "range", "1-5");
+
+    resetArgs(StandardArgs.class);
+    assertTrue(parse(StandardArgs.class, "1mins", "2secs"));
+    assertEquals(ImmutableList.builder()
+        .add(Amount.of(60L, Time.SECONDS))
+        .add(Amount.of(2L, Time.SECONDS)).build(), StandardArgs.POSITIONAL.get());
+  }
+
+  public static class Name {
+    private final String name;
+
+    public Name(String name) {
+      this.name = MorePreconditions.checkNotBlank(name);
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public int hashCode() {
+      return this.name.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return (obj instanceof Name) && name.equals(((Name) obj).name);
+    }
+  }
+
+  @ArgParser
+  public static class NameParser extends NonParameterizedTypeParser<Name> {
+    @Override public Name doParse(String raw) {
+      return new Name(raw);
+    }
+  }
+
+  public static class MeaningOfLife {
+    private final Long answer;
+
+    public MeaningOfLife(Long answer) {
+      this.answer = Preconditions.checkNotNull(answer);
+    }
+
+    @Override
+    public int hashCode() {
+      return this.answer.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      return (obj instanceof MeaningOfLife) && answer.equals(((MeaningOfLife) obj).answer);
+    }
+  }
+
+  public static class Monty extends NonParameterizedTypeParser<MeaningOfLife> {
+    @Override public MeaningOfLife doParse(String raw) {
+      return new MeaningOfLife(42L);
+    }
+  }
+
+  public static class CustomArgs {
+    @CmdLine(name = "custom1", help = "help")
+    static final Arg<Name> NAME_VAL = Arg.create(new Name("jim"));
+
+    @CmdLine(name = "custom2", help = "help", parser = Monty.class)
+    static final Arg<MeaningOfLife> MEANING_VAL = Arg.create(new MeaningOfLife(13L));
+  }
+
+  @Test
+  public void testCustomArgs() {
+    test(CustomArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CustomArgs.NAME_VAL.get(), is(new Name("jane")));
+          }
+        }, "custom1", "jane");
+    test(CustomArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CustomArgs.MEANING_VAL.get(), is(new MeaningOfLife(42L)));
+          }
+        }, "custom2", "jim");
+  }
+
+  @Test
+  public void testHelp() {
+    assertFalse(parse(StandardArgs.class, "-h"));
+    assertFalse(parse(StandardArgs.class, "-help"));
+  }
+
+  @Test
+  public void testAllowsEmptyString() {
+    parse(StandardArgs.class, "-string=");
+    assertThat(StandardArgs.STRING_VAL.get(), is(""));
+
+    resetArgs(StandardArgs.class);
+
+    parse(StandardArgs.class, "-string=''");
+    assertThat(StandardArgs.STRING_VAL.get(), is(""));
+
+    resetArgs(StandardArgs.class);
+
+    parse(StandardArgs.class, "-string=\"\"");
+    assertThat(StandardArgs.STRING_VAL.get(), is(""));
+  }
+
+  public static class CollectionArgs {
+    @CmdLine(name = "stringList", help = "help")
+    static final Arg<List<String>> STRING_LIST = Arg.create(null);
+    @CmdLine(name = "intList", help = "help")
+    static final Arg<List<Integer>> INT_LIST = Arg.create(null);
+    @CmdLine(name = "stringSet", help = "help")
+    static final Arg<Set<String>> STRING_SET = Arg.create(null);
+    @CmdLine(name = "intSet", help = "help")
+    static final Arg<Set<Integer>> INT_SET = Arg.create(null);
+    @CmdLine(name = "stringStringMap", help = "help")
+    static final Arg<Map<String, String>> STRING_STRING_MAP = Arg.create(null);
+    @CmdLine(name = "intIntMap", help = "help")
+    static final Arg<Map<Integer, Integer>> INT_INT_MAP = Arg.create(null);
+    @CmdLine(name = "stringIntMap", help = "help")
+    static final Arg<Map<String, Integer>> STRING_INT_MAP = Arg.create(null);
+    @CmdLine(name = "intStringMap", help = "help")
+    static final Arg<Map<Integer, String>> INT_STRING_MAP = Arg.create(null);
+    @CmdLine(name = "stringStringPair", help = "help")
+    static final Arg<Pair<String, String>> STRING_STRING_PAIR = Arg.create(null);
+    @CmdLine(name = "intIntPair", help = "help")
+    static final Arg<Pair<Integer, Integer>> INT_INT_PAIR = Arg.create(null);
+    @CmdLine(name = "stringTimeAmountPair", help = "help")
+    static final Arg<Pair<String, Amount<Long, Time>>> STRING_TIME_AMOUNT_PAIR = Arg.create(null);
+  }
+
+  @Test
+  public void testCollectionArgs() {
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CollectionArgs.STRING_LIST.get(), is(Arrays.asList("a", "b", "c", "d")));
+          }
+        },
+        "stringList", "a,b,c,d");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CollectionArgs.INT_LIST.get(), is(Arrays.asList(1, 2, 3, 4)));
+          }
+        },
+        "intList", "1, 2, 3, 4");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            Set<String> expected = ImmutableSet.of("a", "b", "c", "d");
+            assertThat(CollectionArgs.STRING_SET.get(), is(expected));
+          }
+        },
+        "stringSet", "a,b,c,d");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            Set<Integer> expected = ImmutableSet.of(1, 2, 3, 4);
+            assertThat(CollectionArgs.INT_SET.get(), is(expected));
+          }
+        },
+        "intSet", "1, 2, 3, 4");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            Map<String, String> expected = ImmutableMap.of("a", "b", "c", "d", "e", "f", "g", "h");
+            assertThat(CollectionArgs.STRING_STRING_MAP.get(), is(expected));
+          }
+        },
+        "stringStringMap", "a=b, c=d, e=f, g=h");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            Map<Integer, Integer> expected = ImmutableMap.of(1, 2, 3, 4, 5, 6, 7, 8);
+            assertThat(CollectionArgs.INT_INT_MAP.get(), is(expected));
+          }
+        },
+        "intIntMap", "1 = 2,3=4, 5=6 ,7=8");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            Map<String, Integer> expected = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4);
+            assertThat(CollectionArgs.STRING_INT_MAP.get(), is(expected));
+          }
+        },
+        "stringIntMap", "a=1  , b=2, c=3   ,d=4");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            Map<Integer, String> expected = ImmutableMap.of(1, "1", 2, "2", 3, "3", 4, "4");
+            assertThat(CollectionArgs.INT_STRING_MAP.get(), is(expected));
+          }
+        },
+        "intStringMap", "  1=1  , 2=2, 3=3,4=4");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CollectionArgs.STRING_STRING_PAIR.get(), is(Pair.of("foo", "bar")));
+          }
+        },
+        "stringStringPair", "foo , bar");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CollectionArgs.INT_INT_PAIR.get(), is(Pair.of(10, 20)));
+          }
+        },
+        "intIntPair", "10    ,20");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(CollectionArgs.STRING_TIME_AMOUNT_PAIR.get(),
+                       is(Pair.of("fred", Amount.of(42L, Time.MINUTES))));
+          }
+        },
+        "stringTimeAmountPair", "fred    ,42mins");
+    test(CollectionArgs.class,
+        new Command() {
+          @Override public void execute() {
+            CollectionArgs.STRING_TIME_AMOUNT_PAIR.get();
+          }
+        },
+        true, "stringTimeAmountPair", "george,1MB");
+
+  }
+
+  static class Serializable1 implements Serializable { }
+  static class Serializable2 implements Serializable { }
+
+  public static class WildcardArgs {
+    @CmdLine(name = "class", help = "help")
+    static final Arg<? extends Class<? extends Serializable>> CLAZZ =
+        Arg.create(Serializable1.class);
+    @CmdLine(name = "classList1", help = "help")
+    static final Arg<List<Class<? extends Serializable>>> CLASS_LIST_1 = Arg.create(null);
+    @CmdLine(name = "classList2", help = "help")
+    static final Arg<List<? extends Class<? extends Serializable>>> CLASS_LIST_2 = Arg.create(null);
+  }
+
+  @Test
+  public void testWildcardArgs() {
+    test(WildcardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertSame(Serializable2.class, WildcardArgs.CLAZZ.get());
+          }
+        },
+        "class", Serializable2.class.getName());
+
+    test(WildcardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            WildcardArgs.CLAZZ.get();
+          }
+        },
+        true, "class", Runnable.class.getName());
+
+    test(WildcardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertEquals(ImmutableList.of(Serializable1.class, Serializable2.class),
+                         WildcardArgs.CLASS_LIST_1.get());
+          }
+        },
+        "classList1", Serializable1.class.getName() + "," + Serializable2.class.getName());
+
+    test(WildcardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertEquals(ImmutableList.of(Serializable2.class), WildcardArgs.CLASS_LIST_2.get());
+          }
+        },
+        "classList2", Serializable2.class.getName());
+
+    test(WildcardArgs.class,
+        new Command() {
+          @Override public void execute() {
+            WildcardArgs.CLASS_LIST_2.get();
+          }
+        },
+        true, "classList2", Serializable1.class.getName() + "," + Runnable.class.getName());
+  }
+
+  @Target(FIELD)
+  @Retention(RUNTIME)
+  public static @interface Equals {
+    String value();
+  }
+
+  @VerifierFor(Equals.class)
+  public static class SameName implements Verifier<Name> {
+    @Override
+    public void verify(Name value, Annotation annotation) {
+      Preconditions.checkArgument(getValue(annotation).equals(value.getName()));
+    }
+
+    @Override
+    public String toString(Class<? extends Name> argType, Annotation annotation) {
+      return "name = " + getValue(annotation);
+    }
+
+    private String getValue(Annotation annotation) {
+      return ((Equals) annotation).value();
+    }
+  }
+
+  public static class VerifyArgs {
+    @Equals("jake") @CmdLine(name = "custom", help = "help")
+    static final Arg<Name> CUSTOM_VAL = Arg.create(new Name("jake"));
+    @NotEmpty @CmdLine(name = "string", help = "help")
+    static final Arg<String> STRING_VAL = Arg.create("string");
+    @NotEmpty @CmdLine(name = "optional_string", help = "help")
+    static final Arg<String> OPTIONAL_STRING_VAL = Arg.create(null);
+    @Positive @CmdLine(name = "int", help = "help")
+    static final Arg<Integer> INT_VAL = Arg.create(1);
+    @NotNegative @CmdLine(name = "long", help = "help")
+    static final Arg<Long> LONG_VAL = Arg.create(0L);
+    @Range(lower = 10, upper = 20) @CmdLine(name = "float", help = "help")
+    static final Arg<Float> FLOAT_VAL = Arg.create(10F);
+    @CmdLine(name = "double", help = "help")
+    static final Arg<Double> DOUBLE_VAL = Arg.create(0D);
+    @CmdLine(name = "bool", help = "help")
+    static final Arg<Boolean> BOOL = Arg.create(false);
+    @CmdLine(name = "arg_without_default", help = "help")
+    static final Arg<Boolean> ARG_WITHOUT_DEFAULT = Arg.create();
+  }
+
+  @Test
+  public void testEnforcesConstraints() {
+    test(VerifyArgs.class,
+        new Command() {
+          @Override public void execute() {
+            assertThat(VerifyArgs.STRING_VAL.get(), is("newstring"));
+            assertThat(VerifyArgs.OPTIONAL_STRING_VAL.get(), nullValue(String.class));
+          }
+        },
+        "string", "newstring");
+
+    testFails(VerifyArgs.class, "custom", "jane");
+    testFails(VerifyArgs.class, "string", "");
+    testFails(VerifyArgs.class, "optional_string", "");
+    testFails(VerifyArgs.class, "int", "0");
+    testFails(VerifyArgs.class, "long", "-1");
+
+    test(VerifyArgs.class,
+        new Command() {
+          @Override public void execute() {
+           assertThat(VerifyArgs.FLOAT_VAL.get(), is(10.5f));
+          }
+        },
+        "float", "10.5");
+    testFails(VerifyArgs.class, "float", "9");
+  }
+
+  @Test
+  public void testJoinKeysToValues() {
+    assertThat(ArgScanner.joinKeysToValues(Arrays.asList("")), is(Arrays.asList("")));
+    assertThat(ArgScanner.joinKeysToValues(Arrays.asList("-a", "b", "-c", "-d")),
+        is(Arrays.asList("-a=b", "-c", "-d")));
+    assertThat(ArgScanner.joinKeysToValues(Arrays.asList("-a='b'", "-c", "-d", "'e'")),
+        is(Arrays.asList("-a='b'", "-c", "-d='e'")));
+    assertThat(ArgScanner.joinKeysToValues(Arrays.asList("-a=-b", "c", "-d", "\"e\"")),
+        is(Arrays.asList("-a=-b", "c", "-d=\"e\"")));
+  }
+
+  public static class ShortHelpArg {
+    @CmdLine(name = "h", help = "help")
+    static final Arg<String> SHORT_HELP = Arg.create("string");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testShortHelpReserved() {
+    parse(ShortHelpArg.class);
+  }
+
+  public static class LongHelpArg {
+    @CmdLine(name = "help", help = "help")
+    static final Arg<String> LONG_HELP = Arg.create("string");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testLongHelpReserved() {
+    parse(LongHelpArg.class);
+  }
+
+  public static class DuplicateNames {
+    @CmdLine(name = "string", help = "help") static final Arg<String> STRING_1 = Arg.create();
+    @CmdLine(name = "string", help = "help") static final Arg<String> STRING_2 = Arg.create();
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testRejectsDuplicates() {
+    parse(DuplicateNames.class, "-string-str");
+  }
+
+  public static class OneRequired {
+    @CmdLine(name = "string1", help = "help")
+    static final Arg<String> STRING_1 = Arg.create(null);
+    @NotNull @CmdLine(name = "string2", help = "help")
+    static final Arg<String> STRING_2 = Arg.create(null);
+  }
+
+  @Test
+  public void testRequiredProvided() {
+    parse(OneRequired.class, "-string2=blah");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMissingRequired() {
+    parse(OneRequired.class, "-string1=blah");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testUnrecognizedArg() {
+    parse(OneRequired.class, "-string2=blah", "-string3=blah");
+  }
+
+  public static class NameClashA {
+    @CmdLine(name = "string", help = "help")
+    static final Arg<String> STRING = Arg.create(null);
+    @CmdLine(name = "boolean", help = "help")
+    static final Arg<Boolean> BOOLEAN = Arg.create(true);
+  }
+
+  public static class NameClashB {
+    @CmdLine(name = "string", help = "help")
+    static final Arg<String> STRING_1 = Arg.create(null);
+    @CmdLine(name = "boolean", help = "help")
+    static final Arg<Boolean> BOOLEAN_1 = Arg.create(true);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testDisallowsShortNameOnArgCollision() {
+    parse(ImmutableList.of(NameClashA.class, NameClashB.class), "-string=blah");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testDisallowsShortNegNameOnArgCollision() {
+    parse(ImmutableList.of(NameClashA.class, NameClashB.class), "-no_boolean");
+  }
+
+  @Test
+  public void testAllowsCanonicalNameOnArgCollision() {
+    // TODO(William Farner): Fix.
+    parse(ImmutableList.of(NameClashA.class, NameClashB.class),
+        "-" + NameClashB.class.getCanonicalName() + ".string=blah");
+  }
+
+  @Test
+  public void testAllowsCanonicalNegNameOnArgCollision() {
+    parse(ImmutableList.of(NameClashA.class, NameClashB.class),
+        "-" + NameClashB.class.getCanonicalName() + ".no_boolean");
+  }
+
+  public static class AmountContainer {
+    @CmdLine(name = "time_amount", help = "help")
+    static final Arg<Amount<Integer, Time>> TIME_AMOUNT = Arg.create(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testBadUnitType() {
+    parse(ImmutableList.of(AmountContainer.class), "-time_amount=1Mb");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testUnrecognizedUnitType() {
+    parse(ImmutableList.of(AmountContainer.class), "-time_amount=1abcd");
+  }
+
+  static class Main1 {
+    @Positional(help = "halp")
+    static final Arg<List<String>> NAMES = Arg.create(null);
+  }
+
+  static class Main2 {
+    @Positional(help = "halp")
+    static final Arg<List<List<String>>> ROSTERS = Arg.create(null);
+  }
+
+  static class Main3 {
+    @Positional(help = "halp")
+    static final Arg<List<Double>> PERCENTILES = Arg.create(null);
+
+    @Positional(help = "halp")
+    static final Arg<List<File>> FILES = Arg.create(null);
+  }
+
+  private void resetMainArgs() {
+    resetArgs(Main1.class);
+    resetArgs(Main2.class);
+    resetArgs(Main3.class);
+  }
+
+  @Test
+  public void testMultiplePositionalsFails() {
+    // Indivdually these should work.
+
+    resetMainArgs();
+    assertTrue(parse(Main1.class, "jack,jill", "laurel,hardy"));
+    assertEquals(ImmutableList.of("jack,jill", "laurel,hardy"),
+        ImmutableList.copyOf(Main1.NAMES.get()));
+
+    resetMainArgs();
+    assertTrue(parse(Main2.class, "jack,jill", "laurel,hardy"));
+    assertEquals(
+        ImmutableList.of(
+            ImmutableList.of("jack", "jill"),
+            ImmutableList.of("laurel", "hardy")),
+        ImmutableList.copyOf(Main2.ROSTERS.get()));
+
+    // But if combined in the same class or across classes the @Positional is ambiguous and we
+    // should fail fast.
+
+    resetMainArgs();
+    try {
+      parse(ImmutableList.of(Main1.class, Main2.class), "jack,jill", "laurel,hardy");
+      fail("Expected more than 1 in-scope @Positional Arg List to trigger a failure.");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    resetMainArgs();
+    try {
+      parse(Main3.class, "50", "90", "99", "99.9");
+      fail("Expected more than 1 in-scope @Positional Arg List to trigger a failure.");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  // TODO(William Farner): Do we want to support nested parameterized args?  If so, need to define a
+  // syntax for that and build it in.
+  //    e.g. List<List<Integer>>, List<Pair<String, String>>
+
+  private static void testFails(Class<?> scope, String arg, String value) {
+    test(scope, null, true, arg, value);
+  }
+
+  private static void test(Class<?> scope, Command validate, String arg, String value) {
+    test(scope, validate, false, arg, value);
+  }
+
+  private static void test(Class<?> scope, Command validate, boolean expectFails, String arg,
+      String value) {
+    String canonicalName = scope.getCanonicalName() + "." + arg;
+
+    if (value.isEmpty()) {
+      testValidate(scope, validate, expectFails, String.format("-%s", arg));
+      testValidate(scope, validate, expectFails, String.format("-%s", canonicalName));
+    } else {
+      testValidate(scope, validate, expectFails, String.format("-%s=%s", arg, value));
+      testValidate(scope, validate, expectFails, String.format("-%s=%s", canonicalName, value));
+      testValidate(scope, validate, expectFails, String.format("-%s='%s'", arg, value));
+      testValidate(scope, validate, expectFails, String.format("-%s='%s'", canonicalName, value));
+      testValidate(scope, validate, expectFails, String.format("-%s=\"%s\"", arg, value));
+      testValidate(scope, validate, expectFails, String.format("-%s=\"%s\"", canonicalName, value));
+      testValidate(scope, validate, expectFails, String.format("-%s", arg), value);
+      testValidate(scope, validate, expectFails, String.format("-%s", canonicalName), value);
+      testValidate(scope, validate, expectFails,
+          String.format("-%s", arg), String.format("'%s'", value));
+      testValidate(scope, validate, expectFails,
+          String.format("-%s", canonicalName), String.format("'%s'", value));
+      testValidate(scope, validate, expectFails, String.format("-%s \"%s\"", arg, value));
+      testValidate(scope, validate, expectFails, String.format("-%s \"%s\"", canonicalName, value));
+      testValidate(scope, validate, expectFails,
+          String.format("-%s", arg), String.format("%s", value));
+      testValidate(scope, validate, expectFails,
+          String.format("-%s", canonicalName), String.format("%s", value));
+    }
+  }
+
+  private static void testValidate(Class<?> scope, Command validate, boolean expectFails,
+      String... args) {
+    resetArgs(scope);
+    IllegalArgumentException exception = null;
+    try {
+      assertTrue(parse(scope, args));
+    } catch (IllegalArgumentException e) {
+      exception = e;
+    }
+
+    if (!expectFails && exception != null) {
+      throw exception;
+    }
+    if (expectFails && exception == null) {
+      fail("Expected exception.");
+    }
+
+    if (validate != null) {
+      validate.execute();
+    }
+    resetArgs(scope);
+  }
+
+  private static void resetArgs(Class<?> scope) {
+    for (Field field : scope.getDeclaredFields()) {
+      if (Arg.class.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers())) {
+        try {
+          ((Arg) field.get(null)).reset();
+        } catch (IllegalAccessException e) {
+          fail(e.getMessage());
+        }
+      }
+    }
+  }
+
+  private static boolean parse(final Class<?> scope, String... args) {
+    return parse(ImmutableList.of(scope), args);
+  }
+
+  private static boolean parse(Iterable<? extends Class<?>> scopes, String... args) {
+    Predicate<Field> filter = Predicates.or(Iterables.transform(scopes, TO_SCOPE_PREDICATE));
+    PrintStream devNull = new PrintStream(ByteStreams.nullOutputStream());
+    return new ArgScanner(devNull).parse(filter, Arrays.asList(args));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/ArgTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/ArgTest.java b/commons/src/test/java/com/twitter/common/args/ArgTest.java
new file mode 100644
index 0000000..3a20eca
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/ArgTest.java
@@ -0,0 +1,37 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+public class ArgTest {
+
+  @Test
+  public void testSetAfterGet() {
+    Arg<Boolean> arg = new Arg<Boolean>(false);
+    arg.get();
+    try {
+      arg.set(true);
+      fail("Expected set after get to throw");
+    } catch (IllegalStateException e) {
+      assertFalse(arg.get());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/ArgsTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/ArgsTest.java b/commons/src/test/java/com/twitter/common/args/ArgsTest.java
new file mode 100644
index 0000000..d6a8b98
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/ArgsTest.java
@@ -0,0 +1,76 @@
+// =================================================================================================
+// Copyright 2013 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import com.twitter.common.args.constraints.NotEmpty;
+import com.twitter.common.args.constraints.Range;
+
+import static junit.framework.Assert.assertEquals;
+
+public class ArgsTest {
+  private static class App {
+    @CmdLine(name = "db", help = "help")
+    private static final Arg<File> DB = Arg.create();
+
+    @NotEmpty
+    @CmdLine(name = "name", help = "help")
+    private final Arg<String> name = Arg.create();
+
+    @Positional(help = "help")
+    private final Arg<List<Integer>> values = Arg.create();
+  }
+
+  @Test
+  public void testMixed() throws IOException {
+    App app = new App();
+
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(App.class), app),
+        ImmutableList.of("-name=bob", "-db=fred", "1", "137"));
+
+    assertEquals(new File("fred"), App.DB.get());
+    assertEquals("bob", app.name.get());
+    assertEquals(ImmutableList.of(1, 137), app.values.get());
+  }
+
+  @Test
+  public void testReentrance() throws IOException {
+    class InnerApp {
+      @Range(lower = 0.0, upper = 1.0)
+      @CmdLine(name = "level", help = "help")
+      private final Arg<Double> level = Arg.create();
+    }
+
+    InnerApp app1 = new InnerApp();
+    InnerApp app2 = new InnerApp();
+
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(InnerApp.class), app1),
+        ImmutableList.of("-level=0.5"));
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(InnerApp.class), app2),
+        ImmutableList.of("-level=0.00729"));
+
+    assertEquals(0.5, app1.level.get(), 0.00001);
+    assertEquals(0.00729, app2.level.get(), 0.00001);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/OptionInfoTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/OptionInfoTest.java b/commons/src/test/java/com/twitter/common/args/OptionInfoTest.java
new file mode 100644
index 0000000..166846d
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/OptionInfoTest.java
@@ -0,0 +1,111 @@
+// =================================================================================================
+// Copyright 2015 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args;
+
+import java.io.File;
+import java.util.List;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.junit.rules.TemporaryFolder;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+public class OptionInfoTest {
+  private static class App {
+    @CmdLine(name = "files", help = "help.", argFile = true)
+    private final Arg<List<File>> files = Arg.<List<File>>create(ImmutableList.<File>of());
+
+    @CmdLine(name = "flag", help = "help.")
+    private final Arg<Boolean> flag = Arg.create();
+  }
+
+  @Rule
+  public TemporaryFolder tmpDir = new TemporaryFolder();
+  private App app;
+
+  @Before
+  public void setUp() throws Exception {
+    app = new App();
+  }
+
+  @Test
+  public void testArgumentFilesCreateFromField() throws Exception {
+    OptionInfo optionInfo = OptionInfo.createFromField(App.class.getDeclaredField("files"), app);
+    assertEquals("files", optionInfo.getName());
+    assertEquals(
+        String.format(OptionInfo.ARG_FILE_HELP_TEMPLATE, "help.", "files", "files"),
+        optionInfo.getHelp());
+    assertTrue(optionInfo.argFile());
+    assertEquals("com.twitter.common.args.OptionInfoTest.App.files",
+        optionInfo.getCanonicalName());
+  }
+
+  @Test
+  public void testArgumentFilesRegularFormat() throws Exception {
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(App.class), app),
+        ImmutableList.of("-files=1.txt,2.txt"));
+    assertEquals(
+        ImmutableList.of(new File("1.txt"), new File("2.txt")),
+        app.files.get());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testArgumentFilesArgFileFormatEmptyFileName() throws Exception {
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(App.class), app),
+        ImmutableList.of("-files=@"));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testArgumentFilesArgFileFormatFileNotExist() throws Exception {
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(App.class), app),
+        ImmutableList.of("-files=@file_does_not_exist.txt"));
+  }
+
+  @Test
+  public void testArgumentFilesArgFileFormat() throws Exception {
+    File argfile = tmpDir.newFile();
+    // Note the '\n' at the end. Some editors auto add a newline at the end so
+    // make sure our arg scanner and parser can deal with this.
+    Files.write("1.txt,2.txt\n", argfile, Charsets.UTF_8);
+    new ArgScanner().parse(Args.from(ArgFilters.selectClass(App.class), app),
+        ImmutableList.of("-files=@" + argfile.getCanonicalPath()));
+    assertEquals(
+        ImmutableList.of(new File("1.txt"), new File("2.txt")),
+        app.files.get());
+  }
+
+  @Test
+  public void testArgumentFlagCreateFromField() throws Exception {
+    OptionInfo optionInfo = OptionInfo.createFromField(App.class.getDeclaredField("flag"), app);
+    assertEquals("flag", optionInfo.getName());
+    assertEquals("help.", optionInfo.getHelp());
+    assertFalse(optionInfo.argFile());
+    assertEquals("com.twitter.common.args.OptionInfoTest.App.flag", optionInfo.getCanonicalName());
+    assertEquals("no_flag", optionInfo.getNegatedName());
+    assertEquals(
+        "com.twitter.common.args.OptionInfoTest.App.no_flag", optionInfo.getCanonicalNegatedName());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/ParsersTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/ParsersTest.java b/commons/src/test/java/com/twitter/common/args/ParsersTest.java
new file mode 100644
index 0000000..f3da00d
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/ParsersTest.java
@@ -0,0 +1,84 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.args.apt.Configuration.ParserInfo;
+import com.twitter.common.args.parsers.NonParameterizedTypeParser;
+import com.twitter.common.args.parsers.PairParser;
+import com.twitter.common.collections.Pair;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+public class ParsersTest {
+
+  private Parsers defaultParsers;
+
+  @Before
+  public void setUp() {
+    defaultParsers =
+        new Parsers(ImmutableMap.<Class<?>, Parser<?>>of(
+            String.class, new StringParser(),
+            Pair.class, new PairParser()));
+  }
+
+  @Test
+  public void testParseTypeFamily() {
+    assertNotNull(defaultParsers.get(TypeToken.of(String.class)));
+
+    class Credentials extends Pair<String, String> {
+      public Credentials(String first, String second) {
+        super(first, second);
+      }
+    }
+    Parser parser = defaultParsers.get(TypeToken.of(Credentials.class));
+    assertNotNull(parser);
+    assertSame(parser, defaultParsers.get(TypeToken.of(Pair.class)));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testNoParser() {
+    class NoParserForMe { }
+    assertNull(defaultParsers.get(TypeToken.of(NoParserForMe.class)));
+  }
+
+  static class StringParser extends NonParameterizedTypeParser<Integer> {
+    @Override public Integer doParse(String raw) throws IllegalArgumentException {
+      return raw.length();
+    }
+  }
+
+  @Test
+  public void testNonPublicParsers() {
+    @SuppressWarnings("unchecked")
+    Parser<Integer> parser = (Parser<Integer>)
+        Parsers.INFO_TO_PARSER.apply(
+            new ParserInfo(Integer.class.getName(), StringParser.class.getName()));
+
+    assertEquals(
+        Integer.valueOf(42),
+        parser.parse(null, null, "themeaningoflifeisfortytwointhebookbyadams"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/argfilterstest/ArgsRoot.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/argfilterstest/ArgsRoot.java b/commons/src/test/java/com/twitter/common/args/argfilterstest/ArgsRoot.java
new file mode 100644
index 0000000..53b9abe
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/argfilterstest/ArgsRoot.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args.argfilterstest;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+/**
+ * @author John Sirois
+ */
+public final class ArgsRoot {
+  @CmdLine(name = "args_root", help = "")
+  static final Arg<String> ARGS_ROOT = Arg.create();
+
+  private ArgsRoot() {
+    // Test class.
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/ArgsA.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/ArgsA.java b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/ArgsA.java
new file mode 100644
index 0000000..b26895c
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/ArgsA.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args.argfilterstest.subpackageA;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+/**
+ * @author John Sirois
+ */
+public final class ArgsA {
+  @CmdLine(name = "args_a", help = "")
+  static final Arg<String> ARGS_A = Arg.create();
+
+  private ArgsA() {
+    // Test class.
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/subsubpackage1/ArgsA1.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/subsubpackage1/ArgsA1.java b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/subsubpackage1/ArgsA1.java
new file mode 100644
index 0000000..9ec7686
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageA/subsubpackage1/ArgsA1.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args.argfilterstest.subpackageA.subsubpackage1;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+/**
+ * @author John Sirois
+ */
+public final class ArgsA1 {
+  @CmdLine(name = "args_a1", help = "")
+  static final Arg<String> ARGS_A1 = Arg.create();
+
+  private ArgsA1() {
+    // Test class.
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageB/ArgsB.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageB/ArgsB.java b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageB/ArgsB.java
new file mode 100644
index 0000000..54a25ba
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageB/ArgsB.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args.argfilterstest.subpackageB;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+/**
+ * @author John Sirois
+ */
+public final class ArgsB {
+  @CmdLine(name = "args_b", help = "")
+  static final Arg<String> ARGS_B = Arg.create();
+
+  private ArgsB() {
+    // Test class.
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageBwithSuffix/ArgsBWithSuffix.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageBwithSuffix/ArgsBWithSuffix.java b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageBwithSuffix/ArgsBWithSuffix.java
new file mode 100644
index 0000000..5c9f12b
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/args/argfilterstest/subpackageBwithSuffix/ArgsBWithSuffix.java
@@ -0,0 +1,32 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.args.argfilterstest.subpackageBwithSuffix;
+
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+
+/**
+ * @author John Sirois
+ */
+public final class ArgsBWithSuffix {
+  @CmdLine(name = "args_b_with_suffix", help = "")
+  static final Arg<String> ARGS_B_WITH_SUFFIX = Arg.create();
+
+  private ArgsBWithSuffix() {
+    // Test class.
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/base/CachingSupplierTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/base/CachingSupplierTest.java b/commons/src/test/java/com/twitter/common/base/CachingSupplierTest.java
new file mode 100644
index 0000000..627d30f
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/base/CachingSupplierTest.java
@@ -0,0 +1,67 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.base;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.testing.easymock.EasyMockTest;
+import com.twitter.common.util.testing.FakeClock;
+
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author William Farner
+ */
+public class CachingSupplierTest extends EasyMockTest {
+
+  private static final Amount<Long, Time> ONE_SECOND = Amount.of(1L, Time.SECONDS);
+
+  private Supplier<String> supplier;
+  private FakeClock clock;
+  private Supplier<String> cache;
+
+  @Before
+  public void setUp() {
+    supplier = createMock(new Clazz<Supplier<String>>() { });
+    clock = new FakeClock();
+    cache = new CachingSupplier<String>(supplier, ONE_SECOND, clock);
+  }
+
+  @Test
+  public void testCaches() {
+    expect(supplier.get()).andReturn("foo");
+    expect(supplier.get()).andReturn("bar");
+
+    control.replay();
+
+    assertEquals("foo", cache.get());
+    assertEquals("foo", cache.get());
+
+    clock.advance(Amount.of(999L, Time.MILLISECONDS));
+    assertEquals("foo", cache.get());
+
+    clock.advance(Amount.of(1L, Time.MILLISECONDS));
+    assertEquals("foo", cache.get());
+
+    clock.advance(Amount.of(1L, Time.MILLISECONDS));
+    assertEquals("bar", cache.get());
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/base/ClosuresTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/base/ClosuresTest.java b/commons/src/test/java/com/twitter/common/base/ClosuresTest.java
new file mode 100644
index 0000000..f2d0adb
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/base/ClosuresTest.java
@@ -0,0 +1,176 @@
+// =================================================================================================
+// Copyright 2011 Twitter, Inc.
+// -------------------------------------------------------------------------------------------------
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this work except in compliance with the License.
+// You may obtain a copy of the License in the LICENSE file, or at:
+//
+//  http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =================================================================================================
+
+package com.twitter.common.base;
+
+import java.io.IOException;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+/**
+ * @author John Sirois
+ */
+public class ClosuresTest extends EasyMockTest {
+
+  private static final Clazz<Closure<Integer>> INT_CLOSURE_CLZ = new Clazz<Closure<Integer>>() { };
+  private static final Clazz<ExceptionalClosure<Integer, IOException>> EXC_INT_CLOSURE_CLZ =
+      new Clazz<ExceptionalClosure<Integer, IOException>>() { };
+
+  @Test(expected = NullPointerException.class)
+  public void testPreconditions() {
+    control.replay();
+
+    Closures.asFunction(null);
+  }
+
+  @Test
+  public void testApply() throws IOException {
+    ExceptionalClosure<Integer, IOException> work = createMock(EXC_INT_CLOSURE_CLZ);
+    work.execute(1);
+    control.replay();
+
+    Function<Integer, Void> workFunction = Closures.asFunction(work);
+    workFunction.apply(1);
+  }
+
+  static class Thrown extends RuntimeException { }
+
+  @Test
+  public void testApplyThrows() throws IOException {
+    ExceptionalClosure<Integer, IOException> work = createMock(EXC_INT_CLOSURE_CLZ);
+    work.execute(1);
+    RuntimeException runtimeException = new Thrown();
+    EasyMock.expectLastCall().andThrow(runtimeException);
+    control.replay();
+
+    Function<Integer, Void> workFunction = Closures.asFunction(work);
+    try {
+      workFunction.apply(1);
+    } catch (Thrown e) {
+      assertSame(runtimeException, e);
+    }
+  }
+
+  @Test
+  public void testApplyThrowsTransparent() throws IOException {
+    Closure<Integer> work = createMock(INT_CLOSURE_CLZ);
+    work.execute(1);
+    RuntimeException runtimeException = new Thrown();
+    EasyMock.expectLastCall().andThrow(runtimeException);
+    control.replay();
+
+    Function<Integer, Void> workFunction = Closures.asFunction(work);
+    try {
+      workFunction.apply(1);
+    } catch (Thrown e) {
+      assertSame(runtimeException, e);
+    }
+  }
+
+  @Test
+  public void testCombine() {
+    Closure<Integer> work1 = createMock(INT_CLOSURE_CLZ);
+    Closure<Integer> work2 = createMock(INT_CLOSURE_CLZ);
+
+    @SuppressWarnings("unchecked") // Needed because type information lost in vargs.
+    Closure<Integer> wrapper = Closures.combine(work1, work2);
+
+    work1.execute(1);
+    work2.execute(1);
+
+    work1.execute(2);
+    work2.execute(2);
+
+    control.replay();
+
+    wrapper.execute(1);
+    wrapper.execute(2);
+  }
+
+  @Test
+  public void testCombineOneThrows() {
+    Closure<Integer> work1 = createMock(INT_CLOSURE_CLZ);
+    Closure<Integer> work2 = createMock(INT_CLOSURE_CLZ);
+    Closure<Integer> work3 = createMock(INT_CLOSURE_CLZ);
+
+    @SuppressWarnings("unchecked") // Needed because type information lost in vargs.
+    Closure<Integer> wrapper = Closures.combine(work1, work2, work3);
+
+    work1.execute(1);
+    expectLastCall().andThrow(new Thrown());
+
+    work1.execute(2);
+    work2.execute(2);
+    expectLastCall().andThrow(new Thrown());
+
+    work1.execute(3);
+    work2.execute(3);
+    work3.execute(3);
+    expectLastCall().andThrow(new Thrown());
+
+    control.replay();
+
+    try {
+      wrapper.execute(1);
+      fail("Should have thrown.");
+    } catch (Thrown e) {
+      // Expected.
+    }
+
+    try {
+      wrapper.execute(2);
+      fail("Should have thrown.");
+    } catch (Thrown e) {
+      // Expected.
+    }
+
+    try {
+      wrapper.execute(3);
+      fail("Should have thrown.");
+    } catch (Thrown e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testFilter() {
+    Predicate<Integer> filter = createMock(new Clazz<Predicate<Integer>>() { });
+    Closure<Integer> work = createMock(INT_CLOSURE_CLZ);
+
+    expect(filter.apply(1)).andReturn(true);
+    work.execute(1);
+
+    expect(filter.apply(2)).andReturn(false);
+
+    Closure<Integer> filtered = Closures.filter(filter, work);
+
+    control.replay();
+
+    filtered.execute(1);
+    filtered.execute(2);
+  }
+}


Mime
View raw message