You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by zm...@apache.org on 2015/08/25 20:19:17 UTC

[03/37] aurora git commit: Import of Twitter Commons.

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/thrift/callers/DeadlineCallerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/thrift/callers/DeadlineCallerTest.java b/commons/src/test/java/com/twitter/common/thrift/callers/DeadlineCallerTest.java
new file mode 100644
index 0000000..99e7b86
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/thrift/callers/DeadlineCallerTest.java
@@ -0,0 +1,122 @@
+// =================================================================================================
+// 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.thrift.callers;
+
+import com.google.common.testing.TearDown;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.thrift.TTimeoutException;
+import org.apache.thrift.async.AsyncMethodCallback;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.Method;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * TODO(William Farner): Test async.
+ *
+ * @author William Farner
+ */
+public class DeadlineCallerTest extends AbstractCallerTest {
+
+  private static final Amount<Long, Time> DEADLINE = Amount.of(100L, Time.MILLISECONDS);
+
+  private ExecutorService executorService;
+
+  private DeadlineCaller makeDeadline(final boolean shouldTimeOut) {
+    final CountDownLatch cancelled = new CountDownLatch(1);
+    if (shouldTimeOut) {
+      addTearDown(new TearDown() {
+        @Override public void tearDown() throws Exception {
+          // This will block forever if cancellation does not occur and interrupt the ~indefinite
+          // sleep.
+          cancelled.await();
+        }
+      });
+    }
+
+    Caller sleepyCaller = new CallerDecorator(caller, false) {
+      @Override public Object call(Method method, Object[] args,
+          @Nullable AsyncMethodCallback callback,
+          @Nullable Amount<Long, Time> connectTimeoutOverride) throws Throwable {
+
+        if (shouldTimeOut) {
+          try {
+            Thread.sleep(Long.MAX_VALUE);
+            fail("Expected late work to be cancelled and interrupted");
+          } catch (InterruptedException e) {
+            cancelled.countDown();
+          }
+        }
+
+        return caller.call(method, args, callback, connectTimeoutOverride);
+      }
+    };
+
+    return new DeadlineCaller(sleepyCaller, false, executorService, DEADLINE);
+  }
+
+  @Before
+  public void setUp() {
+    executorService = Executors.newSingleThreadExecutor();
+  }
+
+  @Test
+  public void testSuccess() throws Throwable {
+    DeadlineCaller deadline = makeDeadline(false);
+    expectCall("foo");
+
+    control.replay();
+
+    assertThat(call(deadline), is("foo"));
+  }
+
+  @Test
+  public void testException() throws Throwable {
+    DeadlineCaller deadline = makeDeadline(false);
+    Throwable exception = new IllegalArgumentException();
+    expectCall(exception);
+
+    control.replay();
+
+    try {
+      call(deadline);
+      fail();
+    } catch (Throwable t) {
+      assertThat(t, is(exception));
+    }
+  }
+
+  @Test(expected = TTimeoutException.class)
+  public void testExceedsDeadline() throws Throwable {
+    DeadlineCaller deadline = makeDeadline(true);
+
+    // No call expected, since we time out before it can be made.
+
+    control.replay();
+
+    call(deadline);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/thrift/callers/RetryingCallerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/thrift/callers/RetryingCallerTest.java b/commons/src/test/java/com/twitter/common/thrift/callers/RetryingCallerTest.java
new file mode 100644
index 0000000..62a2bab
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/thrift/callers/RetryingCallerTest.java
@@ -0,0 +1,153 @@
+// =================================================================================================
+// 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.thrift.callers;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.stats.StatsProvider;
+
+import static org.easymock.EasyMock.expect;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * TODO(William Farner): Test async.
+ *
+ * @author William Farner
+ */
+public class RetryingCallerTest extends AbstractCallerTest {
+
+  private static final int NUM_RETRIES = 2;
+
+  private static final ImmutableSet<Class<? extends Exception>> NO_RETRYABLE =
+      ImmutableSet.of();
+  private static final ImmutableSet<Class<? extends Exception>> RETRYABLE =
+      ImmutableSet.<Class<? extends Exception>>of(IllegalArgumentException.class);
+
+  private StatsProvider statsProvider;
+
+  @Before
+  public void mySetUp() {
+    statsProvider = createMock(StatsProvider.class);
+  }
+
+  @Test
+  public void testSuccess() throws Throwable {
+    expectCall("foo");
+
+    control.replay();
+
+    RetryingCaller retry = makeRetry(false, NO_RETRYABLE);
+    assertThat(call(retry), is("foo"));
+    assertThat(memoizeGetCounter.get(methodA).get(), is(0L));
+  }
+
+  @Test
+  public void testException() throws Throwable {
+    Throwable exception = nonRetryable();
+    expectCall(exception);
+
+    control.replay();
+
+    RetryingCaller retry = makeRetry(false, NO_RETRYABLE);
+    try {
+      call(retry);
+      fail();
+    } catch (Throwable t) {
+      assertThat(t, is(exception));
+    }
+    assertThat(memoizeGetCounter.get(methodA).get(), is(0L));
+  }
+
+  @Test
+  public void testRetriesSuccess() throws Throwable {
+    expectCall(retryable());
+    expectCall(retryable());
+    expectCall("foo");
+
+    control.replay();
+
+    RetryingCaller retry = makeRetry(false, RETRYABLE);
+    assertThat(call(retry), is("foo"));
+    assertThat(memoizeGetCounter.get(methodA).get(), is((long) NUM_RETRIES));
+  }
+
+  @Test
+  public void testRetryLimit() throws Throwable {
+    expectCall(retryable());
+    expectCall(retryable());
+    Throwable exception = retryable();
+    expectCall(exception);
+
+    control.replay();
+
+    RetryingCaller retry = makeRetry(false, RETRYABLE);
+    try {
+      call(retry);
+      fail();
+    } catch (Throwable t) {
+      assertThat(t, is(exception));
+    }
+    assertThat(memoizeGetCounter.get(methodA).get(), is(2L));
+  }
+
+  private Throwable retryable() {
+    return new IllegalArgumentException();
+  }
+
+  private Throwable nonRetryable() {
+    return new NullPointerException();
+  }
+
+  private LoadingCache<Method, AtomicLong> memoizeGetCounter = CacheBuilder.newBuilder().build(
+      new CacheLoader<Method, AtomicLong>() {
+        @Override public AtomicLong load(Method method) {
+          AtomicLong atomicLong = new AtomicLong();
+          expect(statsProvider.makeCounter("test_" + method.getName() + "_retries"))
+              .andReturn(atomicLong);
+          return atomicLong;
+        }
+      });
+
+  @Override
+  protected void expectCall(String returnValue) throws Throwable {
+    super.expectCall(returnValue);
+    memoizeGetCounter.get(methodA);
+  }
+
+  @Override
+  protected void expectCall(Throwable thrown) throws Throwable {
+    super.expectCall(thrown);
+    memoizeGetCounter.get(methodA);
+  }
+
+  private RetryingCaller makeRetry(boolean async,
+      ImmutableSet<Class<? extends Exception>> retryableExceptions) {
+    return new RetryingCaller(caller, async, statsProvider, "test", NUM_RETRIES,
+        retryableExceptions, false);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/BackoffDeciderTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/BackoffDeciderTest.java b/commons/src/test/java/com/twitter/common/util/BackoffDeciderTest.java
new file mode 100644
index 0000000..b24452d
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/BackoffDeciderTest.java
@@ -0,0 +1,327 @@
+// =================================================================================================
+// 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.util;
+
+import com.google.common.collect.Sets;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.testing.FakeClock;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+import static org.easymock.EasyMock.createControl;
+import static org.easymock.EasyMock.expect;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author William Farner
+ */
+public class BackoffDeciderTest {
+  private static final String NAME = "test_decider";
+
+  private IMocksControl control;
+
+  private FakeClock clock;
+  private Random random;
+
+  @Before
+  public void setUp() {
+    control = createControl();
+    random = control.createMock(Random.class);
+
+    clock = new FakeClock();
+  }
+
+  private BackoffDecider.Builder builder(String name) {
+    return new BackoffDecider.Builder(name)
+        .withSeedSize(1)
+        .withRequestWindow(Amount.of(10L, Time.SECONDS))
+        .withBucketCount(100)
+        .withClock(clock)
+        .withRandom(random);
+  }
+
+  @After
+  public void verify() {
+    control.verify();
+  }
+
+  @Test
+  public void testAllSuccess() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    run(decider, 10, Result.SUCCESS, State.NORMAL);
+  }
+
+  @Test
+  public void testAllFailures() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    run(decider, 10, Result.FAILURE, State.BACKOFF);
+  }
+
+  @Test
+  public void testSingleFailure() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    run(decider, 10, Result.SUCCESS, State.NORMAL);
+    run(decider, 1, Result.FAILURE, State.NORMAL);
+  }
+
+  @Test
+  public void testBelowThreshold() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).withTolerateFailureRate(0.5).build();
+    run(decider, 5, Result.SUCCESS, State.NORMAL);
+    run(decider, 5, Result.FAILURE, State.NORMAL);
+  }
+
+  @Test
+  public void testAtThreshold() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).withTolerateFailureRate(0.49).build();
+    run(decider, 51, Result.SUCCESS, State.NORMAL);
+    run(decider, 49, Result.FAILURE, State.NORMAL);
+  }
+
+  @Test
+  public void testAboveThreshold() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).withTolerateFailureRate(0.49).build();
+    run(decider, 51, Result.SUCCESS, State.NORMAL);
+    run(decider, 49, Result.FAILURE, State.NORMAL);
+    run(decider, 1, Result.FAILURE, State.BACKOFF);
+  }
+
+  @Test
+  public void testRecoversFromBackoff() {
+    // Backoff for the single request during the recovery period.
+    expect(random.nextDouble()).andReturn(1d);
+
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    decider.addFailure();
+    assertThat(decider.shouldBackOff(), is(true));
+
+    // Enter recovery period.
+    clock.waitFor(101);
+    assertThat(decider.shouldBackOff(), is(true));
+
+    // Enter normal period.
+    clock.waitFor(101);
+    assertThat(decider.shouldBackOff(), is(false));
+  }
+
+  @Test
+  public void testLinearRecovery() {
+    for (int i = 0; i < 10; i++) {
+      expect(random.nextDouble()).andReturn(0.1 * i + 0.01);  // Above threshold - back off.
+      expect(random.nextDouble()).andReturn(0.1 * i - 0.01);  // Below threshold - allow request.
+    }
+
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    decider.addFailure(); // Moves into backoff state.
+    assertThat(decider.shouldBackOff(), is(true));
+
+    // Enter recovery period.
+    clock.waitFor(101);
+
+    // Step linearly through recovery period (100 ms).
+    for (int i = 0; i < 10; i++) {
+      clock.waitFor(10);
+      assertThat(decider.shouldBackOff(), is(true));
+      assertThat(decider.shouldBackOff(), is(false));
+    }
+  }
+
+  @Test
+  public void testExponentialBackoff() {
+    // Don't back off during recovery period.
+    expect(random.nextDouble()).andReturn(0d).atLeastOnce();
+
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    List<Integer> backoffDurationsMs = Arrays.asList(
+        100, 200, 400, 800, 1600, 3200, 6400, 10000, 10000);
+
+    assertThat(decider.shouldBackOff(), is(false));
+
+    // normal -> backoff
+    decider.addFailure();
+    assertThat(decider.shouldBackOff(), is(true));
+
+    for (int backoffDurationMs : backoffDurationsMs) {
+      assertThat(decider.shouldBackOff(), is(true));
+
+      // backoff -> recovery
+      clock.waitFor(backoffDurationMs + 1);
+      assertThat(decider.shouldBackOff(), is(false));
+
+      // recovery -> backoff
+      decider.addFailure();
+    }
+  }
+
+  @Test
+  public void testRequestsExpire() {
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+    run(decider, 10, Result.SUCCESS, State.NORMAL);
+    run(decider, 10, Result.FAILURE, State.NORMAL);
+    assertThat(decider.shouldBackOff(), is(false));
+
+    // Depends on request window of 10 seconds, with 100 buckets.
+    clock.waitFor(10000);
+    run(decider, 1, Result.SUCCESS, State.NORMAL);
+    assertThat(decider.shouldBackOff(), is(false));
+    assertThat(decider.requests.totalRequests, is(21L));
+    assertThat(decider.requests.totalFailures, is(10L));
+
+    // Requests should have decayed out of the window.
+    clock.waitFor(101);
+    run(decider, 1, Result.SUCCESS, State.NORMAL);
+    assertThat(decider.shouldBackOff(), is(false));
+    assertThat(decider.requests.totalRequests, is(2L));
+    assertThat(decider.requests.totalFailures, is(0L));
+  }
+
+  @Test
+  public void testAllBackendsDontBackoff() {
+    // Back off for all requests during recovery period.
+    expect(random.nextDouble()).andReturn(1d); // decider2 in recovery.
+    expect(random.nextDouble()).andReturn(0d); // decider3 in recovery.
+
+    control.replay();
+
+    Set<BackoffDecider> group = Sets.newHashSet();
+    BackoffDecider decider1 = builder(NAME + 1).groupWith(group).build();
+    BackoffDecider decider2 = builder(NAME + 2).groupWith(group).build();
+    BackoffDecider decider3 = builder(NAME + 3).groupWith(group).build();
+
+    // Two of three backends start backing off.
+    decider1.addFailure();
+    assertThat(decider1.shouldBackOff(), is(true));
+
+    decider2.addFailure();
+    assertThat(decider2.shouldBackOff(), is(true));
+
+    // Since all but 1 backend is backing off, we switch out of backoff mode.
+    assertThat(decider3.shouldBackOff(), is(false));
+    decider3.addFailure();
+    assertThat(decider1.shouldBackOff(), is(false));
+    assertThat(decider2.shouldBackOff(), is(false));
+    assertThat(decider3.shouldBackOff(), is(false));
+
+    // Begin recovering one backend, others will return to recovery.
+    decider1.addSuccess();
+    assertThat(decider1.shouldBackOff(), is(false)); // Still thinks others are backing off.
+    assertThat(decider2.shouldBackOff(), is(false)); // Realizes decider1 is up, moves to recovery.
+    assertThat(decider2.shouldBackOff(), is(true));  // In recovery.
+    assertThat(decider3.shouldBackOff(), is(false)); // Realizes 1 & 2 are up, moves to recovery.
+    assertThat(decider3.shouldBackOff(), is(false));  // In recovery.
+  }
+
+  @Test
+  public void testOneBackendDoesntAffectOthers() {
+    control.replay();
+
+    Set<BackoffDecider> group = Sets.newHashSet();
+    BackoffDecider decider1 = builder(NAME + 1).groupWith(group).build();
+    BackoffDecider decider2 = builder(NAME + 2).groupWith(group).build();
+    BackoffDecider decider3 = builder(NAME + 3).groupWith(group).build();
+
+    // One backend starts failing.
+    run(decider1, 10, Result.SUCCESS, State.NORMAL);
+    run(decider2, 10, Result.SUCCESS, State.NORMAL);
+    run(decider3, 10, Result.FAILURE, State.BACKOFF);
+
+    // Other backends should remain normal.
+    run(decider1, 10, Result.SUCCESS, State.NORMAL);
+    run(decider2, 10, Result.SUCCESS, State.NORMAL);
+  }
+
+  @Test
+  public void testPreventsBackoffFlapping() {
+    // Permit requests during the backoff period.
+    expect(random.nextDouble()).andReturn(0d).atLeastOnce();
+
+    control.replay();
+
+    BackoffDecider decider = builder(NAME).build();
+
+    // Simulate 20 threads being permitted to send a request.
+    for (int i = 0; i < 20; i++) assertThat(decider.shouldBackOff(), is(false));
+
+    // The first 4 threads succeed.
+    for (int i = 0; i < 4; i++) decider.addSuccess();
+    assertThat(decider.shouldBackOff(), is(false));
+
+    // The next 6 fail, triggering backoff mode.
+    for (int i = 0; i < 6; i++) decider.addFailure();
+    assertThat(decider.shouldBackOff(), is(true));
+
+    // The next 10 succeed, but we are already backing off...so we should not move out of backoff.
+    for (int i = 0; i < 10; i++) decider.addSuccess();
+    assertThat(decider.shouldBackOff(), is(true));
+
+    // Attempt to push the decider into a higher backoff period.
+    for (int i = 0; i < 10; i++) decider.addFailure();
+
+    // Verify that the initial backoff period is in effect.
+    clock.waitFor(101);
+    assertThat(decider.shouldBackOff(), is(false));
+  }
+
+  private enum Result {
+    SUCCESS, FAILURE
+  }
+
+  private enum State {
+    BACKOFF, NORMAL
+  }
+
+  private void run(BackoffDecider decider, int numRequests, Result result, State state) {
+    for (int i = 0; i < numRequests; i++) {
+      if (result == Result.SUCCESS) {
+        decider.addSuccess();
+      } else {
+        decider.addFailure();
+      }
+
+      boolean backingOff = state == State.BACKOFF;
+      assertThat(decider.shouldBackOff(), is(backingOff));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/BackoffHelperTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/BackoffHelperTest.java b/commons/src/test/java/com/twitter/common/util/BackoffHelperTest.java
new file mode 100644
index 0000000..274878c
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/BackoffHelperTest.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.util;
+
+import com.twitter.common.base.ExceptionalSupplier;
+import com.twitter.common.base.Supplier;
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import org.easymock.IMocksControl;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.easymock.EasyMock.createControl;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.*;
+
+/**
+ * @author John Sirois
+ */
+public class BackoffHelperTest extends EasyMockTest {
+  private Clock clock;
+  private BackoffStrategy backoffStrategy;
+  private BackoffHelper backoffHelper;
+
+  @Before
+  public void setUp() {
+    clock = createMock(Clock.class);
+    backoffStrategy = createMock(BackoffStrategy.class);
+    backoffHelper = new BackoffHelper(clock, backoffStrategy);
+  }
+
+  @Test
+  public void testDoUntilSuccess() throws Exception {
+    Supplier<Boolean> task = createMock(new Clazz<Supplier<Boolean>>() { });
+
+    expect(task.get()).andReturn(false);
+    expect(backoffStrategy.shouldContinue(0L)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(0L)).andReturn(42L);
+    clock.waitFor(42L);
+    expect(task.get()).andReturn(true);
+
+    control.replay();
+
+    backoffHelper.doUntilSuccess(task);
+
+    control.verify();
+  }
+
+  @Test
+  public void testDoUntilResult() throws Exception {
+    Supplier<String> task = createMock(new Clazz<Supplier<String>>() { });
+
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(0)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(0)).andReturn(42L);
+    clock.waitFor(42L);
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(42L)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(42L)).andReturn(37L);
+    clock.waitFor(37L);
+    expect(task.get()).andReturn("jake");
+
+    control.replay();
+
+    assertEquals("jake", backoffHelper.doUntilResult(task));
+
+    control.verify();
+  }
+
+  @Test
+  public void testDoUntilResultTransparentException() throws Exception {
+    ExceptionalSupplier<String, IOException> task =
+        createMock(new Clazz<ExceptionalSupplier<String, IOException>>() { });
+
+    IOException thrown = new IOException();
+    expect(task.get()).andThrow(thrown);
+
+    control.replay();
+
+    try {
+      backoffHelper.doUntilResult(task);
+      fail("Expected exception to be bubbled");
+    } catch (IOException e) {
+      assertSame(thrown, e);
+    }
+
+    control.verify();
+  }
+
+  @Test
+  public void testDoUntilResultMaxSuccess() throws Exception {
+    Supplier<String> task = createMock(new Clazz<Supplier<String>>() { });
+
+    BackoffHelper maxBackoffHelper = new BackoffHelper(clock, backoffStrategy);
+
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(0L)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(0)).andReturn(42L);
+    clock.waitFor(42L);
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(42L)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(42L)).andReturn(37L);
+    clock.waitFor(37L);
+    expect(task.get()).andReturn("jake");
+
+    control.replay();
+
+    assertEquals("jake", maxBackoffHelper.doUntilResult(task));
+
+    control.verify();
+  }
+
+  @Test
+  public void testDoUntilResultMaxReached() throws Exception {
+    Supplier<String> task = createMock(new Clazz<Supplier<String>>() { });
+
+    BackoffHelper maxBackoffHelper = new BackoffHelper(clock, backoffStrategy);
+
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(0L)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(0)).andReturn(42L);
+    clock.waitFor(42L);
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(42L)).andReturn(true);
+    expect(backoffStrategy.calculateBackoffMs(42L)).andReturn(37L);
+    clock.waitFor(37L);
+    expect(task.get()).andReturn(null);
+    expect(backoffStrategy.shouldContinue(37L)).andReturn(false);
+
+    control.replay();
+
+    try {
+      maxBackoffHelper.doUntilResult(task);
+      fail("Expected max retry failure");
+    } catch (BackoffHelper.BackoffStoppedException e) {
+      // expected
+    }
+
+    control.verify();
+  }
+
+  @Test
+  public void testDoUntilSuccessTransparentException() throws Exception {
+    Supplier<Boolean> task = createMock(new Clazz<Supplier<Boolean>>() { });
+
+    IllegalArgumentException thrown = new IllegalArgumentException();
+    expect(task.get()).andThrow(thrown);
+
+    control.replay();
+
+    try {
+      backoffHelper.doUntilSuccess(task);
+      fail("Expected exception to be bubbled");
+    } catch (IllegalArgumentException e) {
+      assertSame(thrown, e);
+    }
+
+    control.verify();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/LowResClockTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/LowResClockTest.java b/commons/src/test/java/com/twitter/common/util/LowResClockTest.java
new file mode 100644
index 0000000..d8961ed
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/LowResClockTest.java
@@ -0,0 +1,172 @@
+// =================================================================================================
+// Copyright 2014 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.util;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.easymock.Capture;
+import org.easymock.IAnswer;
+import org.junit.Test;
+
+import com.twitter.common.base.Command;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.testing.FakeClock;
+
+import static org.easymock.EasyMock.anyBoolean;
+import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.captureLong;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class LowResClockTest {
+
+  /**
+   * A FakeClock that overrides the {@link FakeClock#advance(Amount) advance} method to allow a
+   * co-operating thread to execute a synchronous action via {@link #doOnAdvance(Command)}.
+   */
+  static class WaitingFakeClock extends FakeClock {
+    private final SynchronousQueue<CountDownLatch> signalQueue =
+        new SynchronousQueue<CountDownLatch>();
+
+    @Override
+    public void advance(Amount<Long, Time> period) {
+      super.advance(period);
+      CountDownLatch signal = new CountDownLatch(1);
+      try {
+        signalQueue.put(signal);
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+      try {
+        signal.await();
+      } catch (InterruptedException e) {
+        // ignore
+      }
+    }
+
+    void doOnAdvance(Command action) throws InterruptedException {
+      CountDownLatch signal = signalQueue.take();
+      action.execute();
+      signal.countDown();
+    }
+  }
+
+  static class Tick implements Command {
+    private final Clock clock;
+    private final long period;
+    private final Runnable advancer;
+    private long time;
+
+    Tick(Clock clock, long startTime, long period, Runnable advancer) {
+      this.clock = clock;
+      time = startTime;
+      this.period = period;
+      this.advancer = advancer;
+    }
+
+    @Override
+    public void execute() {
+      if (clock.nowMillis() >= time + period) {
+        advancer.run();
+        time = clock.nowMillis();
+      }
+    }
+  }
+
+  @Test
+  public void testLowResClock() {
+    final WaitingFakeClock clock = new WaitingFakeClock();
+    final long start = clock.nowMillis();
+
+    ScheduledExecutorService mockExecutor = createMock(ScheduledExecutorService.class);
+    final Capture<Runnable> runnable = new Capture<Runnable>();
+    final Capture<Long> period = new Capture<Long>();
+    mockExecutor.scheduleAtFixedRate(capture(runnable), anyLong(), captureLong(period),
+        eq(TimeUnit.MILLISECONDS));
+
+    expectLastCall().andAnswer(new IAnswer<ScheduledFuture<?>>() {
+      public ScheduledFuture<?> answer() {
+        final Thread ticker = new Thread() {
+          @Override
+          public void run() {
+            Tick tick = new Tick(clock, start, period.getValue(), runnable.getValue());
+            try {
+              while (true) {
+                clock.doOnAdvance(tick);
+              }
+            } catch (InterruptedException e) {
+              /* terminate */
+            }
+          }
+        };
+        ticker.setDaemon(true);
+        ticker.start();
+        final ScheduledFuture<?> future = createMock(ScheduledFuture.class);
+        final AtomicBoolean stopped = new AtomicBoolean(false);
+        expect(future.isCancelled()).andAnswer(new IAnswer<Boolean>() {
+          @Override
+          public Boolean answer() throws Throwable {
+            return stopped.get();
+          }
+        }).anyTimes();
+        expect(future.cancel(anyBoolean())).andAnswer(new IAnswer<Boolean>() {
+          @Override
+          public Boolean answer() throws Throwable {
+            ticker.interrupt();
+            stopped.set(true);
+            return true;
+          }
+        });
+        replay(future);
+        return future;
+      }
+    });
+    replay(mockExecutor);
+
+    LowResClock lowRes = new LowResClock(Amount.of(1L, Time.SECONDS), mockExecutor, clock);
+
+    long t = lowRes.nowMillis();
+    clock.advance(Amount.of(100L, Time.MILLISECONDS));
+    assertEquals(t, lowRes.nowMillis());
+
+    clock.advance(Amount.of(900L, Time.MILLISECONDS));
+    assertEquals(t + 1000, lowRes.nowMillis());
+
+    clock.advance(Amount.of(100L, Time.MILLISECONDS));
+    assertEquals(t + 1000, lowRes.nowMillis());
+
+    lowRes.close();
+    try {
+      lowRes.nowMillis();
+      fail("Closed clock should throw exception!");
+    } catch (IllegalStateException e) {
+      /* expected */
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/QueueDrainerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/QueueDrainerTest.java b/commons/src/test/java/com/twitter/common/util/QueueDrainerTest.java
new file mode 100644
index 0000000..88f76fa
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/QueueDrainerTest.java
@@ -0,0 +1,62 @@
+// =================================================================================================
+// 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.util;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+/**
+ * @author Srinivasan Rajagopal
+ */
+public class QueueDrainerTest extends EasyMockTest {
+  private Executor taskExecutor;
+  private BlockingQueue<RetryingRunnable> blockingQueue;
+  private QueueDrainer queueDrainer;
+
+  @Before
+  public void setUp() {
+    taskExecutor = createMock(Executor.class);
+    blockingQueue = createMock(new Clazz<BlockingQueue<RetryingRunnable>>() { });
+    queueDrainer = new QueueDrainer<RetryingRunnable>(taskExecutor, blockingQueue);
+  }
+
+  @Test
+  public void testDrainsQueue() throws Exception {
+    RetryingRunnable task = createMock(RetryingRunnable.class);
+    expect(blockingQueue.poll()).andReturn(task);
+    taskExecutor.execute(task);
+    control.replay();
+    replay();
+    queueDrainer.run();
+  }
+
+  @Test
+  public void testEmptyQueue() throws Exception {
+    expect(blockingQueue.poll()).andReturn(null);
+    control.replay();
+    replay();
+    queueDrainer.run();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/RateLimitedCommandExecutorTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/RateLimitedCommandExecutorTest.java b/commons/src/test/java/com/twitter/common/util/RateLimitedCommandExecutorTest.java
new file mode 100644
index 0000000..2f4d479
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/RateLimitedCommandExecutorTest.java
@@ -0,0 +1,118 @@
+// =================================================================================================
+// 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.util;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.easymock.Capture;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.base.Command;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expectLastCall;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Srinivasan Rajagopal
+ */
+public class RateLimitedCommandExecutorTest extends EasyMockTest {
+  private ScheduledExecutorService taskExecutor;
+  private Amount<Long, Time> intervalBetweenRequests;
+  private RateLimitedCommandExecutor rateLimiter;
+  private Command command;
+  private BlockingQueue<RetryingRunnable<?>> queue;
+  private Runnable queueDrainer;
+
+  @Before
+  public void setUp() throws Exception {
+    command = createMock(Command.class);
+    taskExecutor = createMock(ScheduledExecutorService.class);
+    queue = createMock(new Clazz<BlockingQueue<RetryingRunnable<?>>>() {});
+    queueDrainer = createMock(Runnable.class);
+    intervalBetweenRequests = Amount.of(100L, Time.MILLISECONDS);
+  }
+
+  private RateLimitedCommandExecutor createLimiter() {
+    return new RateLimitedCommandExecutor(
+        taskExecutor,
+        intervalBetweenRequests,
+        queueDrainer,
+        queue);
+  }
+
+  @Test
+  public void testFixedRateClientDequeueIsInvoked() throws Exception {
+    Capture<Runnable> runnableCapture = createCapture();
+    expect(taskExecutor.scheduleWithFixedDelay(
+        capture(runnableCapture),
+        eq(0L),
+        eq((long) intervalBetweenRequests.as(Time.MILLISECONDS)),
+        eq(TimeUnit.MILLISECONDS))).andReturn(null);
+    control.replay();
+
+    rateLimiter = createLimiter();
+    assertTrue(runnableCapture.hasCaptured());
+    assertNotNull(runnableCapture.getValue());
+  }
+
+  @Test
+  public void testEnqueue() throws Exception {
+    expect(taskExecutor.scheduleWithFixedDelay((Runnable) anyObject(),
+        eq(0L),
+        eq((long) intervalBetweenRequests.as(Time.MILLISECONDS)),
+        eq(TimeUnit.MILLISECONDS))).andReturn(null);
+
+    Capture<RetryingRunnable> runnableTaskCapture = createCapture();
+    expect(queue.add(capture(runnableTaskCapture))).andReturn(true);
+    control.replay();
+
+    rateLimiter = createLimiter();
+    rateLimiter.execute("name", command, RuntimeException.class, 1, intervalBetweenRequests);
+    assertTrue(runnableTaskCapture.hasCaptured());
+  }
+
+  @Test
+  public void testDrainQueueCommandHandlesException() {
+    Capture<Runnable> runnableCapture = createCapture();
+    expect(taskExecutor.scheduleWithFixedDelay(
+        capture(runnableCapture),
+        eq(0L),
+        eq((long) intervalBetweenRequests.as(Time.MILLISECONDS)),
+        eq(TimeUnit.MILLISECONDS))).andReturn(null);
+    queueDrainer.run();
+    expectLastCall().andThrow(new RuntimeException());
+
+    control.replay();
+    rateLimiter = createLimiter();
+
+    //Execute the runnable to ensure the exception does not propagate
+    // and potentially kill threads in the executor service.
+    runnableCapture.getValue().run();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/SamplerTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/SamplerTest.java b/commons/src/test/java/com/twitter/common/util/SamplerTest.java
new file mode 100644
index 0000000..f798513
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/SamplerTest.java
@@ -0,0 +1,70 @@
+// =================================================================================================
+// 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.util;
+
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+
+/**
+ * @author William Farner
+ */
+public class SamplerTest {
+
+  private IMocksControl control;
+
+  private Random random;
+
+  @Before
+  public void setUp() throws Exception {
+    control = createControl();
+
+    random = control.createMock(Random.class);
+  }
+
+  @After
+  public void verify() {
+    control.verify();
+  }
+
+  @Test
+  public void testThresholdWorks() {
+    for (int i = 0; i <= 100; i++) {
+      expect(random.nextDouble()).andReturn(0.01 * i);
+    }
+
+    control.replay();
+
+    Sampler sampler = new Sampler(25, random);
+
+    for (int i = 0; i <= 100; i++) {
+      assertThat(sampler.select(), is(i < 25));
+    }
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testRejectsNegativePercent() {
+    control.replay();
+
+    new Sampler(-10, random);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/StateMachineTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/StateMachineTest.java b/commons/src/test/java/com/twitter/common/util/StateMachineTest.java
new file mode 100644
index 0000000..fe59928
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/StateMachineTest.java
@@ -0,0 +1,420 @@
+// =================================================================================================
+// 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.util;
+
+import com.google.common.collect.ImmutableSet;
+
+import com.twitter.common.base.Closure;
+import com.twitter.common.base.Closures;
+import com.twitter.common.base.Command;
+import com.twitter.common.base.Commands;
+import com.twitter.common.base.ExceptionalSupplier;
+import com.twitter.common.base.Supplier;
+import com.twitter.common.testing.easymock.EasyMockTest;
+import com.twitter.common.util.StateMachine.Transition;
+import com.twitter.common.util.StateMachine.Rule;
+
+import org.junit.Test;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests the functionality of StateMachine.
+ *
+ * @author William Farner
+ */
+public class StateMachineTest extends EasyMockTest {
+  private static final String NAME = "State machine.";
+
+  private static final String A = "A";
+  private static final String B = "B";
+  private static final String C = "C";
+  private static final String D = "D";
+
+  @Test
+  public void testEmptySM() {
+    control.replay();
+
+    try {
+      StateMachine.builder(NAME).build();
+      fail();
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testMachineNoInit() {
+    control.replay();
+
+    try {
+      StateMachine.<String>builder(NAME)
+          .addState(Rule.from(A).to(B))
+          .build();
+      fail();
+    } catch (IllegalStateException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testBasicFSM() {
+    control.replay();
+
+    StateMachine<String> fsm = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .addState(Rule.from(B).to(C))
+        .addState(Rule.from(C).to(D))
+        .build();
+
+    assertThat(fsm.getState(), is(A));
+    changeState(fsm, B);
+    changeState(fsm, C);
+    changeState(fsm, D);
+  }
+
+  @Test
+  public void testLoopingFSM() {
+    control.replay();
+
+    StateMachine<String> fsm = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .addState(Rule.from(B).to(C))
+        .addState(Rule.from(C).to(B, D))
+        .build();
+
+    assertThat(fsm.getState(), is(A));
+    changeState(fsm, B);
+    changeState(fsm, C);
+    changeState(fsm, B);
+    changeState(fsm, C);
+    changeState(fsm, B);
+    changeState(fsm, C);
+    changeState(fsm, D);
+  }
+
+  @Test
+  public void testMachineUnknownState() {
+    control.replay();
+
+    StateMachine<String> fsm = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .addState(Rule.from(B).to(C))
+        .build();
+
+    assertThat(fsm.getState(), is(A));
+    changeState(fsm, B);
+    changeState(fsm, C);
+    changeStateFail(fsm, D);
+  }
+
+  @Test
+  public void testMachineBadTransition() {
+    control.replay();
+
+    StateMachine<String> fsm = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .addState(Rule.from(B).to(C))
+        .build();
+
+    assertThat(fsm.getState(), is(A));
+    changeState(fsm, B);
+    changeState(fsm, C);
+    changeStateFail(fsm, B);
+  }
+
+  @Test
+  public void testMachineSelfTransitionAllowed() {
+    control.replay();
+
+    StateMachine<String> fsm = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(A))
+        .build();
+
+    assertThat(fsm.getState(), is(A));
+    changeState(fsm, A);
+    changeState(fsm, A);
+  }
+
+  @Test
+  public void testMachineSelfTransitionDisallowed() {
+    control.replay();
+
+    StateMachine<String> fsm = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .build();
+
+    assertThat(fsm.getState(), is(A));
+    changeStateFail(fsm, A);
+    changeStateFail(fsm, A);
+  }
+
+  @Test
+  public void testCheckStateMatches() {
+    control.replay();
+
+    StateMachine<String> stateMachine = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .build();
+    stateMachine.checkState(A);
+    stateMachine.transition(B);
+    stateMachine.checkState(B);
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testCheckStateFails() {
+    control.replay();
+
+    StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .build()
+        .checkState(B);
+  }
+
+  @Test
+  public void testDoInStateMatches() {
+    control.replay();
+
+    StateMachine<String> stateMachine = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B))
+        .build();
+
+    int amount = stateMachine.doInState(A, new Supplier<Integer>() {
+      @Override public Integer get() {
+        return 42;
+      }
+    });
+    assertThat(amount, is(42));
+
+    stateMachine.transition(B);
+
+    String name = stateMachine.doInState(B, new Supplier<String>() {
+      @Override public String get() {
+        return "jake";
+      }
+    });
+    assertThat(name, is("jake"));
+  }
+
+  @Test
+  public void testDoInStateConcurrently() throws InterruptedException {
+    control.replay();
+
+    final StateMachine<String> stateMachine = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(A, B)
+        .build();
+
+    final BlockingQueue<Integer> results = new LinkedBlockingQueue<Integer>();
+
+    final CountDownLatch supplier1Proceed = new CountDownLatch(1);
+    final ExceptionalSupplier<Void, RuntimeException> supplier1 =
+        Commands.asSupplier(new Command() {
+          @Override public void execute() {
+            results.offer(1);
+            try {
+              supplier1Proceed.await();
+            } catch (InterruptedException e) {
+              throw new RuntimeException(e);
+            }
+          }
+        });
+
+    final CountDownLatch supplier2Proceed = new CountDownLatch(1);
+    final ExceptionalSupplier<Void, RuntimeException> supplier2 =
+        Commands.asSupplier(new Command() {
+          @Override public void execute() {
+            results.offer(2);
+            try {
+              supplier2Proceed.await();
+            } catch (InterruptedException e) {
+              throw new RuntimeException(e);
+            }
+          }
+        });
+
+    Thread thread1 = new Thread(new Runnable() {
+      @Override public void run() {
+        stateMachine.doInState(A, supplier1);
+      }
+    });
+
+    Thread thread2 = new Thread(new Runnable() {
+      @Override public void run() {
+        stateMachine.doInState(A, supplier2);
+      }
+    });
+
+    Thread thread3 = new Thread(new Runnable() {
+      @Override public void run() {
+        stateMachine.transition(B);
+      }
+    });
+
+    thread1.start();
+    thread2.start();
+
+    Integer result1 = results.take();
+    Integer result2 = results.take();
+    // we know 1 and 2 have the read lock held
+
+    thread3.start(); // should be blocked by read locks in place
+
+    assertThat(ImmutableSet.of(result1, result2), is(ImmutableSet.of(1, 2)));
+    assertTrue(results.isEmpty());
+
+    supplier1Proceed.countDown();
+    supplier2Proceed.countDown();
+
+    thread1.join();
+    thread2.join();
+    thread3.join();
+
+    assertThat(B, is(stateMachine.getState()));
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testDoInStateFails() {
+    control.replay();
+
+    StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(A, B)
+        .build()
+        .doInState(B, Commands.asSupplier(Commands.NOOP));
+  }
+
+  @Test
+  public void testNoThrowOnInvalidTransition() {
+    control.replay();
+
+    StateMachine<String> machine = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(A, B)
+        .throwOnBadTransition(false)
+        .build();
+
+    machine.transition(C);
+    assertThat(machine.getState(), is(A));
+  }
+
+  private static final Clazz<Closure<Transition<String>>> TRANSITION_CLOSURE_CLZ =
+      new Clazz<Closure<Transition<String>>>() {};
+
+  @Test
+  public void testTransitionCallbacks() {
+    Closure<Transition<String>> anyTransition = createMock(TRANSITION_CLOSURE_CLZ);
+    Closure<Transition<String>> fromA = createMock(TRANSITION_CLOSURE_CLZ);
+    Closure<Transition<String>> fromB = createMock(TRANSITION_CLOSURE_CLZ);
+
+    Transition<String> aToB = new Transition<String>(A, B, true);
+    anyTransition.execute(aToB);
+    fromA.execute(aToB);
+
+    Transition<String> bToB = new Transition<String>(B, B, false);
+    anyTransition.execute(bToB);
+    fromB.execute(bToB);
+
+    Transition<String> bToC = new Transition<String>(B, C, true);
+    anyTransition.execute(bToC);
+    fromB.execute(bToC);
+
+    anyTransition.execute(new Transition<String>(C, B, true));
+
+    Transition<String> bToD = new Transition<String>(B, D, true);
+    anyTransition.execute(bToD);
+    fromB.execute(bToD);
+
+    control.replay();
+
+    StateMachine<String> machine = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule.from(A).to(B).withCallback(fromA))
+        .addState(Rule.from(B).to(C, D).withCallback(fromB))
+        .addState(Rule.from(C).to(B))
+        .addState(Rule.from(D).noTransitions())
+        .onAnyTransition(anyTransition)
+        .throwOnBadTransition(false)
+        .build();
+
+    machine.transition(B);
+    machine.transition(B);
+    machine.transition(C);
+    machine.transition(B);
+    machine.transition(D);
+  }
+
+  @Test
+  public void testFilteredTransitionCallbacks() {
+    Closure<Transition<String>> aToBHandler = createMock(TRANSITION_CLOSURE_CLZ);
+    Closure<Transition<String>> impossibleHandler = createMock(TRANSITION_CLOSURE_CLZ);
+
+    aToBHandler.execute(new Transition<String>(A, B, true));
+
+    control.replay();
+
+    StateMachine<String> machine = StateMachine.<String>builder(NAME)
+        .initialState(A)
+        .addState(Rule
+            .from(A).to(B, C)
+            .withCallback(Closures.filter(Transition.to(B), aToBHandler)))
+        .addState(Rule.from(B).to(A)
+            .withCallback(Closures.filter(Transition.to(B), impossibleHandler)))
+        .addState(Rule.from(C).noTransitions())
+        .build();
+
+    machine.transition(B);
+    machine.transition(A);
+    machine.transition(C);
+  }
+
+  private static void changeState(StateMachine<String> machine, String to, boolean expectAllowed) {
+    boolean allowed = true;
+    try {
+      machine.transition(to);
+      assertThat(machine.getState(), is(to));
+    } catch (StateMachine.IllegalStateTransitionException e) {
+      allowed = false;
+    }
+
+    assertThat(allowed, is(expectAllowed));
+  }
+
+  private static void changeState(StateMachine<String> machine, String to) {
+    changeState(machine, to, true);
+  }
+
+  private static void changeStateFail(StateMachine<String> machine, String to) {
+    changeState(machine, to, false);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/TruncatedBinaryBackoffTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/TruncatedBinaryBackoffTest.java b/commons/src/test/java/com/twitter/common/util/TruncatedBinaryBackoffTest.java
new file mode 100644
index 0000000..1932be5
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/TruncatedBinaryBackoffTest.java
@@ -0,0 +1,93 @@
+// =================================================================================================
+// 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.util;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author John Sirois
+ */
+public class TruncatedBinaryBackoffTest {
+  @Test(expected = NullPointerException.class)
+  public void testNullInitialBackoffRejected() {
+    new TruncatedBinaryBackoff(null, Amount.of(1L, Time.SECONDS));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testZeroInitialBackoffRejected() {
+    new TruncatedBinaryBackoff(Amount.of(0L, Time.SECONDS), Amount.of(1L, Time.SECONDS));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testNegativeInitialBackoffRejected() {
+    new TruncatedBinaryBackoff(Amount.of(-1L, Time.SECONDS), Amount.of(1L, Time.SECONDS));
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void testNullMaximumBackoffRejected() {
+    new TruncatedBinaryBackoff(Amount.of(1L, Time.SECONDS), null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMaximumBackoffLessThanInitialBackoffRejected() {
+    new TruncatedBinaryBackoff(Amount.of(2L, Time.SECONDS), Amount.of(1L, Time.SECONDS));
+  }
+
+  @Test
+  public void testCalculateBackoffMs() {
+    TruncatedBinaryBackoff backoff =
+        new TruncatedBinaryBackoff(Amount.of(1L, Time.MILLISECONDS),
+            Amount.of(6L, Time.MILLISECONDS));
+
+    try {
+      backoff.calculateBackoffMs(-1L);
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(1, backoff.calculateBackoffMs(0));
+    assertEquals(2, backoff.calculateBackoffMs(1));
+    assertEquals(4, backoff.calculateBackoffMs(2));
+    assertEquals(6, backoff.calculateBackoffMs(4));
+    assertEquals(6, backoff.calculateBackoffMs(8));
+  }
+
+  @Test
+  public void testCalculateBackoffStop() {
+    TruncatedBinaryBackoff backoff =
+        new TruncatedBinaryBackoff(Amount.of(1L, Time.MILLISECONDS),
+            Amount.of(6L, Time.MILLISECONDS), true);
+
+    assertTrue(backoff.shouldContinue(0));
+    assertEquals(1, backoff.calculateBackoffMs(0));
+    assertTrue(backoff.shouldContinue(1));
+    assertEquals(2, backoff.calculateBackoffMs(1));
+    assertTrue(backoff.shouldContinue(2));
+    assertEquals(4, backoff.calculateBackoffMs(2));
+    assertTrue(backoff.shouldContinue(4));
+    assertEquals(6, backoff.calculateBackoffMs(4));
+    assertFalse(backoff.shouldContinue(6));
+    assertEquals(6, backoff.calculateBackoffMs(8));
+    assertFalse(backoff.shouldContinue(8));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/caching/CachingMethodProxyTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/caching/CachingMethodProxyTest.java b/commons/src/test/java/com/twitter/common/util/caching/CachingMethodProxyTest.java
new file mode 100644
index 0000000..a942182
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/caching/CachingMethodProxyTest.java
@@ -0,0 +1,260 @@
+// =================================================================================================
+// 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.util.caching;
+
+import com.google.common.base.Predicate;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.easymock.EasyMock.createControl;
+import static org.easymock.EasyMock.expect;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author William Farner
+ */
+public class CachingMethodProxyTest {
+
+  private CachingMethodProxy<Math> proxyBuilder;
+  private Math uncachedMath;
+  private Math cachedMath;
+  private Cache<List, Integer> intCache;
+  private Predicate<Integer> intFilter;
+
+  private IMocksControl control;
+
+  @Before
+  @SuppressWarnings("unchecked")
+  public void setUp() {
+    control = createControl();
+    uncachedMath = control.createMock(Math.class);
+    intCache = control.createMock(Cache.class);
+    intFilter = control.createMock(Predicate.class);
+
+    proxyBuilder = CachingMethodProxy.proxyFor(uncachedMath, Math.class);
+    cachedMath = proxyBuilder.getCachingProxy();
+  }
+
+  @After
+  public void verifyControl() {
+    control.verify();
+  }
+
+  @Test
+  public void testCaches() throws Exception {
+    expectUncachedAdd(1, 2, true);
+    expectUncachedAdd(3, 4, true);
+    expect(intCache.get(Arrays.asList(1, 2))).andReturn(3);
+    expect(intCache.get(Arrays.asList(3, 4))).andReturn(7);
+
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.sum(0, 0), intCache, intFilter)
+        .prepare();
+    assertThat(cachedMath.sum(1, 2), is(3));
+    assertThat(cachedMath.sum(3, 4), is(7));
+    assertThat(cachedMath.sum(1, 2), is(3));
+    assertThat(cachedMath.sum(3, 4), is(7));
+  }
+
+  @Test
+  public void testIgnoresUncachedMethod() throws Exception {
+    expect(uncachedMath.sub(2, 1)).andReturn(1);
+    expect(uncachedMath.sub(2, 1)).andReturn(1);
+
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.sum(0, 0), intCache, intFilter)
+        .prepare();
+    assertThat(cachedMath.sub(2, 1), is(1));
+    assertThat(cachedMath.sub(2, 1), is(1));
+  }
+
+  @Test
+  public void testFilterValue() throws Exception {
+    expectUncachedAdd(1, 2, true);
+    expectUncachedAdd(3, 4, false);
+    expect(intCache.get(Arrays.asList(1, 2))).andReturn(3);
+
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.sum(0, 0), intCache, intFilter)
+        .prepare();
+    assertThat(cachedMath.sum(1, 2), is(3));
+    assertThat(cachedMath.sum(3, 4), is(7));
+    assertThat(cachedMath.sum(1, 2), is(3));
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testRequiresOneCache() throws Exception {
+    control.replay();
+
+    proxyBuilder.prepare();
+  }
+
+  @Test
+  public void testExceptionThrown() throws Exception {
+    List<Integer> args = Arrays.asList(1, 2);
+    expect(intCache.get(args)).andReturn(null);
+
+    Math.AddException thrown = new Math.AddException();
+    expect(uncachedMath.sum(1, 2)).andThrow(thrown);
+
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.sum(0, 0), intCache, intFilter)
+            .prepare();
+    try {
+      cachedMath.sum(1, 2);
+    } catch (Math.AddException e) {
+      assertSame(e, thrown);
+    }
+  }
+
+  /* TODO(William Farner): Re-enable once the TODO for checking return value/cache value types is done.
+  @Test(expected = IllegalArgumentException.class)
+  public void testCacheValueAndMethodReturnTypeMismatch() throws Exception {
+    control.replay();
+
+    cachedMath.addDouble(0, 0);
+    proxyBuilder.cache(1, intCache, intFilter)
+        .prepare();
+  }
+  */
+
+  @Test(expected = IllegalStateException.class)
+  public void testRejectsCacheSetupAfterPrepare() throws Exception {
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.sum(0, 0), intCache, intFilter)
+        .prepare();
+    proxyBuilder.cache(null, intCache, intFilter);
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void testIgnoresNullValues() throws Exception {
+    // Null return values should not even be considered for entry into the cache, and therefore
+    // should not be passed to the filter.
+
+    Cache<List, Math> crazyCache = control.createMock(Cache.class);
+    Predicate<Math> crazyFilter = control.createMock(Predicate.class);
+
+    expect(crazyCache.get(Arrays.asList(null, null))).andReturn(null);
+    expect(uncachedMath.crazyMath(null, null)).andReturn(null);
+
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.crazyMath(null, null), crazyCache, crazyFilter)
+        .prepare();
+
+    cachedMath.crazyMath(null, null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  @SuppressWarnings("unchecked")
+  public void testRejectsVoidReturn() throws Exception {
+    Cache<List, Void> voidCache = control.createMock(Cache.class);
+    Predicate<Void> voidFilter = control.createMock(Predicate.class);
+
+    control.replay();
+
+    cachedMath.doSomething(null);
+    proxyBuilder.cache(null, voidCache, voidFilter);
+  }
+
+  @Test(expected = IllegalStateException.class)
+  @SuppressWarnings("unchecked")
+  public void testFailsNoCachedCall() throws Exception {
+    Cache<List, Void> voidCache = control.createMock(Cache.class);
+    Predicate<Void> voidFilter = control.createMock(Predicate.class);
+
+    control.replay();
+
+    // No method call was recorded on the proxy, so the builder doesn't know what to cache.
+    proxyBuilder.cache(null, voidCache, voidFilter);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  @SuppressWarnings("unchecked")
+  public void testRejectsZeroArgMethods() throws Exception {
+    Cache<List, Math> mathCache = control.createMock(Cache.class);
+    Predicate<Math> mathFilter = control.createMock(Predicate.class);
+
+    control.replay();
+
+    proxyBuilder.cache(cachedMath.doNothing(), mathCache, mathFilter);
+  }
+
+  @Test
+  public void testAllowsSuperclassMethod() throws Exception {
+    SubMath subMath = control.createMock(SubMath.class);
+
+    List<Integer> args = Arrays.asList(1, 2);
+    expect(intCache.get(args)).andReturn(null);
+    expect(subMath.sum(1, 2)).andReturn(3);
+    expect(intFilter.apply(3)).andReturn(true);
+    intCache.put(args, 3);
+
+    control.replay();
+
+    Method add = SubMath.class.getMethod("sum", int.class, int.class);
+
+    CachingMethodProxy<SubMath> proxyBuilder = CachingMethodProxy.proxyFor(subMath, SubMath.class);
+    SubMath cached = proxyBuilder.getCachingProxy();
+    proxyBuilder.cache(cached.sum(0, 0), intCache, intFilter)
+        .prepare();
+
+    cached.sum(1, 2);
+  }
+
+  private void expectUncachedAdd(int a, int b, boolean addToCache) throws Math.AddException {
+    List<Integer> args = Arrays.asList(a, b);
+    expect(intCache.get(args)).andReturn(null);
+    expect(uncachedMath.sum(a, b)).andReturn(a + b);
+    expect(intFilter.apply(a + b)).andReturn(addToCache);
+    if (addToCache) intCache.put(args, a  + b);
+  }
+
+  private interface Math {
+    public int sum(int a, int b) throws AddException;
+
+    public double addDouble(double a, double b) throws AddException;
+
+    public int sub(int a, int b);
+
+    public Math crazyMath(Math a, Math b);
+
+    public Math doNothing();
+
+    public void doSomething(Math a);
+
+    class AddException extends Exception {}
+  }
+
+  private interface SubMath extends Math {
+    public int otherSum(int a, int b);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/caching/LRUCacheTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/caching/LRUCacheTest.java b/commons/src/test/java/com/twitter/common/util/caching/LRUCacheTest.java
new file mode 100644
index 0000000..42af0f1
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/caching/LRUCacheTest.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.util.caching;
+
+import com.google.common.collect.Lists;
+import com.twitter.common.base.Closure;
+import com.twitter.common.collections.Pair;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests the LRUCache class.
+ *
+ * @author William Farner
+ */
+public class LRUCacheTest {
+
+  @Test
+  public void testEvicts() {
+    int cacheSize = 10;
+    int inserts = 100;
+    LRUCache<Integer, Integer> cache = LRUCache.<Integer, Integer>builder()
+        .maxSize(cacheSize)
+        .build();
+    for (int i = 0; i < inserts; i++) {
+      cache.put(i, i);
+      assertThat(cache.size(), is(Math.min(i + 1, cacheSize)));
+    }
+  }
+
+  @Test
+  public void testEvictsLRU() {
+    int cacheSize = 10;
+
+    final List<Integer> evictedKeys = Lists.newLinkedList();
+
+    Closure<Pair<Integer, Integer>> listener = new Closure<Pair<Integer, Integer>>() {
+        @Override public void execute(Pair<Integer, Integer> evicted) {
+          evictedKeys.add(evicted.getFirst());
+        }
+    };
+
+    LRUCache<Integer, Integer> cache = LRUCache.<Integer, Integer>builder()
+        .maxSize(cacheSize)
+        .evictionListener(listener)
+        .build();
+
+    for (int i = 0; i < cacheSize; i++) {
+      cache.put(i, i);
+    }
+
+    // Access all elements except '3'.
+    for (int access : Arrays.asList(0, 7, 2, 8, 4, 6, 9, 1, 5)) {
+      assertNotNull(cache.get(access));
+    }
+
+    assertThat(evictedKeys.isEmpty(), is(true));
+
+    // This should trigger the eviction.
+    cache.put(cacheSize + 1, cacheSize + 1);
+
+    assertThat(evictedKeys, is(Arrays.asList(3)));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingExecutorServiceTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingExecutorServiceTest.java b/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingExecutorServiceTest.java
new file mode 100644
index 0000000..59a335f
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingExecutorServiceTest.java
@@ -0,0 +1,111 @@
+package com.twitter.common.util.concurrent;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.google.common.testing.TearDown;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+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 static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ExceptionHandlingExecutorServiceTest extends EasyMockTest {
+  private static final RuntimeException EXCEPTION = new RuntimeException();
+
+  private ExecutorService executorService;
+  private Thread.UncaughtExceptionHandler signallingHandler;
+
+  @Before
+  public void setUp() throws Exception {
+    signallingHandler = createMock(Thread.UncaughtExceptionHandler.class);
+    executorService = MoreExecutors.exceptionHandlingExecutor(
+        Executors.newCachedThreadPool(new ThreadFactoryBuilder()
+            .setNameFormat("ExceptionHandlingExecutorServiceTest-%d")
+            .build()),
+        signallingHandler);
+
+    final ExecutorServiceShutdown executorServiceShutdown = new ExecutorServiceShutdown(
+        executorService, Amount.of(3L, Time.SECONDS));
+
+    addTearDown(new TearDown() {
+      @Override
+      public void tearDown() throws Exception {
+        executorServiceShutdown.execute();
+      }
+    });
+  }
+
+  @Test
+  public void testSubmitRunnable() throws Exception {
+    Runnable runnable = createMock(Runnable.class);
+    runnable.run();
+
+    control.replay();
+
+    executorService.submit(runnable).get();
+  }
+
+  @Test
+  public void testSubmitFailingRunnable() throws Exception {
+    signallingHandler.uncaughtException(anyObject(Thread.class), eq(EXCEPTION));
+    Runnable runnable = createMock(Runnable.class);
+    runnable.run();
+    expectLastCall().andThrow(EXCEPTION);
+
+    control.replay();
+
+    try {
+      executorService.submit(runnable).get();
+      fail(EXCEPTION.getClass().getSimpleName() + " should be thrown.");
+    } catch (ExecutionException e) {
+      assertEquals(EXCEPTION, e.getCause());
+    }
+  }
+
+  @Test
+  public void testSubmitCallable() throws Exception {
+    Integer returnValue = 123;
+    Callable<Integer> callable = createMock(new Clazz<Callable<Integer>>() {});
+    callable.call();
+    expectLastCall().andReturn(returnValue);
+
+    control.replay();
+
+    assertEquals(returnValue, executorService.submit(callable).get());
+  }
+
+  @Test
+  public void testSubmitFailingCallable() throws Exception {
+    signallingHandler.uncaughtException(anyObject(Thread.class), eq(EXCEPTION));
+    Callable<Void> callable = createMock(new Clazz<Callable<Void>>() {});
+    expect(callable.call()).andThrow(EXCEPTION);
+
+    control.replay();
+
+    try {
+      executorService.submit(callable).get();
+      fail(EXCEPTION.getClass().getSimpleName() + " should be thrown.");
+    } catch (ExecutionException e) {
+      assertEquals(EXCEPTION, e.getCause());
+    }
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void testNullHandler() throws Exception {
+    control.replay();
+    MoreExecutors.exceptionHandlingExecutor(executorService, null);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingScheduledExecutorServiceTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingScheduledExecutorServiceTest.java b/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingScheduledExecutorServiceTest.java
new file mode 100644
index 0000000..6abe260
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/concurrent/ExceptionHandlingScheduledExecutorServiceTest.java
@@ -0,0 +1,124 @@
+package com.twitter.common.util.concurrent;
+
+import java.lang.Exception;
+import java.lang.IllegalArgumentException;
+import java.lang.NullPointerException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.testing.TearDown;
+
+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.concurrent.MoreExecutors;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ExceptionHandlingScheduledExecutorServiceTest extends EasyMockTest {
+  private static final RuntimeException EXCEPTION = new RuntimeException();
+
+  private ScheduledExecutorService executorService;
+  private Thread.UncaughtExceptionHandler signallingHandler;
+
+  @Before
+  public void setUp() throws Exception {
+    signallingHandler = createMock(Thread.UncaughtExceptionHandler.class);
+    executorService = MoreExecutors.exceptionHandlingExecutor(
+        Executors.newSingleThreadScheduledExecutor(), signallingHandler);
+
+    final ExecutorServiceShutdown executorServiceShutdown = new ExecutorServiceShutdown(
+        executorService, Amount.of(3L, Time.SECONDS));
+
+    addTearDown(new TearDown() {
+      @Override
+      public void tearDown() throws Exception {
+        executorServiceShutdown.execute();
+      }
+    });
+  }
+
+  @Test
+  public void testSubmitRunnable() throws Exception {
+    signallingHandler.uncaughtException(anyObject(Thread.class), eq(EXCEPTION));
+    Runnable runnable = createMock(Runnable.class);
+    runnable.run();
+    expectLastCall().andThrow(EXCEPTION);
+
+    control.replay();
+
+    try {
+      executorService.submit(runnable).get();
+      fail(EXCEPTION.getClass().getSimpleName() + " should be thrown.");
+    } catch (ExecutionException e) {
+      assertEquals(EXCEPTION, e.getCause());
+    }
+  }
+
+  @Test
+  public void testSubmitCallable() throws Exception {
+    signallingHandler.uncaughtException(anyObject(Thread.class), eq(EXCEPTION));
+    Callable<Void> callable = createMock(new Clazz<Callable<Void>>() {});
+    expect(callable.call()).andThrow(EXCEPTION);
+
+    control.replay();
+
+    try {
+      executorService.submit(callable).get();
+      fail(EXCEPTION.getClass().getSimpleName() + " should be thrown.");
+    } catch (ExecutionException e) {
+      assertEquals(EXCEPTION, e.getCause());
+    }
+  }
+
+  @Test
+  public void testScheduleAtFixedRate() throws Exception {
+    signallingHandler.uncaughtException(anyObject(Thread.class), eq(EXCEPTION));
+    Runnable runnable = createMock(Runnable.class);
+    runnable.run();
+    expectLastCall().andThrow(EXCEPTION);
+
+    control.replay();
+
+    try {
+      executorService.scheduleAtFixedRate(runnable, 0, 10, TimeUnit.MILLISECONDS).get();
+      fail(EXCEPTION.getClass().getSimpleName() + " should be thrown.");
+    } catch (ExecutionException e) {
+      assertEquals(EXCEPTION, e.getCause());
+    }
+  }
+
+  @Test
+  public void testScheduleWithFixedDelay() throws Exception {
+    signallingHandler.uncaughtException(anyObject(Thread.class), eq(EXCEPTION));
+    Runnable runnable = createMock(Runnable.class);
+    runnable.run();
+    expectLastCall().andThrow(EXCEPTION);
+
+    control.replay();
+
+    try {
+      executorService.scheduleWithFixedDelay(runnable, 0, 10, TimeUnit.MILLISECONDS).get();
+      fail(EXCEPTION.getClass().getSimpleName() + " should be thrown.");
+    } catch (ExecutionException e) {
+      assertEquals(EXCEPTION, e.getCause());
+    }
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void testNullHandler() throws Exception {
+    control.replay();
+    MoreExecutors.exceptionHandlingExecutor(executorService, null);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/templating/StringTemplateHelperTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/templating/StringTemplateHelperTest.java b/commons/src/test/java/com/twitter/common/util/templating/StringTemplateHelperTest.java
new file mode 100644
index 0000000..306288c
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/templating/StringTemplateHelperTest.java
@@ -0,0 +1,80 @@
+package com.twitter.common.util.templating;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+
+import org.antlr.stringtemplate.StringTemplate;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.twitter.common.base.Closure;
+import com.twitter.common.util.templating.StringTemplateHelper.TemplateException;
+
+import static org.junit.Assert.assertEquals;
+
+public class StringTemplateHelperTest {
+
+  private StringTemplateHelper templateHelper;
+
+  @Before
+  public void setUp() {
+    templateHelper = new StringTemplateHelper(getClass(), "template", false);
+  }
+
+  private static class Item {
+    final String name;
+    final int price;
+
+    private Item(String name, int price) {
+      this.name = name;
+      this.price = price;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public int getPrice() {
+      return price;
+    }
+  }
+
+  @Test
+  public void testFillTemplate() throws Exception {
+    StringWriter output = new StringWriter();
+    templateHelper.writeTemplate(output, new Closure<StringTemplate>() {
+      @Override public void execute(StringTemplate template) {
+        template.setAttribute("header", "Prices");
+        template.setAttribute("items", Arrays.asList(
+            new Item("banana", 50),
+            new Item("car", 2),
+            new Item("jupiter", 200)
+        ));
+        template.setAttribute("footer", "The End");
+      }
+    });
+    String expected = "Prices\n"
+        + "\n  The banana costs $50."
+        + "\n  The car costs $2."
+        + "\n  The jupiter costs $200.\n"
+        + "\n\nThe End";
+    assertEquals(expected, output.toString());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMissingTemplate() throws Exception {
+    new StringTemplateHelper(getClass(), "missing_template", false);
+  }
+
+  private static class CustomException extends RuntimeException {
+  }
+
+  @Test(expected = CustomException.class)
+  public void testClosureError() throws Exception {
+    templateHelper.writeTemplate(new StringWriter(), new Closure<StringTemplate>() {
+      @Override public void execute(StringTemplate template) {
+        throw new CustomException();
+      }
+    });
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/86a547b9/commons/src/test/java/com/twitter/common/util/testing/FakeClockTest.java
----------------------------------------------------------------------
diff --git a/commons/src/test/java/com/twitter/common/util/testing/FakeClockTest.java b/commons/src/test/java/com/twitter/common/util/testing/FakeClockTest.java
new file mode 100644
index 0000000..9ea14fe
--- /dev/null
+++ b/commons/src/test/java/com/twitter/common/util/testing/FakeClockTest.java
@@ -0,0 +1,73 @@
+// =================================================================================================
+// 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.util.testing;
+
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author John Sirois
+ */
+public class FakeClockTest {
+  private FakeTicker fakeTicker;
+
+  @Before
+  public void setUp() {
+    fakeTicker = new FakeTicker();
+  }
+
+  @Test
+  public void testNow() throws InterruptedException {
+    assertEquals("A fake clock should start out at time 0", 0, fakeTicker.read());
+
+    fakeTicker.setNowNanos(42L);
+    assertEquals("A fake clock's time should only be controled by setNow", 42L, fakeTicker.read());
+
+    long start = System.nanoTime();
+    Thread.sleep(10L);
+    assertTrue(System.nanoTime() - start > 0);
+    assertEquals("A fake clock's time should only be controled by setNow", 42L, fakeTicker.read());
+  }
+
+  @Test
+  public void testWaitFor() {
+    fakeTicker.waitNanos(42L);
+    assertEquals(42L, fakeTicker.read());
+
+    fakeTicker.waitNanos(42L);
+    assertEquals(84L, fakeTicker.read());
+  }
+
+  @Test
+  public void testAdvance() {
+    fakeTicker.advance(Amount.of(42L, Time.NANOSECONDS));
+    assertEquals(42L, fakeTicker.read());
+
+    fakeTicker.advance(Amount.of(42L, Time.NANOSECONDS));
+    assertEquals(84L, fakeTicker.read());
+
+    fakeTicker.advance(Amount.of(-42L, Time.NANOSECONDS));
+    assertEquals(42L, fakeTicker.read());
+
+    fakeTicker.advance(Amount.of(-43L, Time.NANOSECONDS));
+    assertEquals(-1L,fakeTicker.read());
+  }
+}