Return-Path: X-Original-To: apmail-aurora-commits-archive@minotaur.apache.org Delivered-To: apmail-aurora-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 3A75918244 for ; Tue, 25 Aug 2015 18:19:16 +0000 (UTC) Received: (qmail 72453 invoked by uid 500); 25 Aug 2015 18:19:16 -0000 Delivered-To: apmail-aurora-commits-archive@aurora.apache.org Received: (qmail 72354 invoked by uid 500); 25 Aug 2015 18:19:16 -0000 Mailing-List: contact commits-help@aurora.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@aurora.apache.org Delivered-To: mailing list commits@aurora.apache.org Received: (qmail 72333 invoked by uid 99); 25 Aug 2015 18:19:16 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 25 Aug 2015 18:19:16 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id ACAC9E35D0; Tue, 25 Aug 2015 18:19:15 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: zmanji@apache.org To: commits@aurora.apache.org Date: Tue, 25 Aug 2015 18:19:17 -0000 Message-Id: In-Reply-To: <2d86d301903d4a1c81757199842a5e58@git.apache.org> References: <2d86d301903d4a1c81757199842a5e58@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [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 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 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> NO_RETRYABLE = + ImmutableSet.of(); + private static final ImmutableSet> RETRYABLE = + ImmutableSet.>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 memoizeGetCounter = CacheBuilder.newBuilder().build( + new CacheLoader() { + @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> 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 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 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 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 task = createMock(new Clazz>() { }); + + 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 task = createMock(new Clazz>() { }); + + 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 task = + createMock(new Clazz>() { }); + + 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 task = createMock(new Clazz>() { }); + + 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 task = createMock(new Clazz>() { }); + + 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 task = createMock(new Clazz>() { }); + + 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 signalQueue = + new SynchronousQueue(); + + @Override + public void advance(Amount 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 = new Capture(); + final Capture period = new Capture(); + mockExecutor.scheduleAtFixedRate(capture(runnable), anyLong(), captureLong(period), + eq(TimeUnit.MILLISECONDS)); + + expectLastCall().andAnswer(new IAnswer>() { + 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() { + @Override + public Boolean answer() throws Throwable { + return stopped.get(); + } + }).anyTimes(); + expect(future.cancel(anyBoolean())).andAnswer(new IAnswer() { + @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 blockingQueue; + private QueueDrainer queueDrainer; + + @Before + public void setUp() { + taskExecutor = createMock(Executor.class); + blockingQueue = createMock(new Clazz>() { }); + queueDrainer = new QueueDrainer(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 intervalBetweenRequests; + private RateLimitedCommandExecutor rateLimiter; + private Command command; + private BlockingQueue> queue; + private Runnable queueDrainer; + + @Before + public void setUp() throws Exception { + command = createMock(Command.class); + taskExecutor = createMock(ScheduledExecutorService.class); + queue = createMock(new Clazz>>() {}); + 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 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 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 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.builder(NAME) + .addState(Rule.from(A).to(B)) + .build(); + fail(); + } catch (IllegalStateException e) { + // Expected. + } + } + + @Test + public void testBasicFSM() { + control.replay(); + + StateMachine fsm = StateMachine.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 fsm = StateMachine.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 fsm = StateMachine.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 fsm = StateMachine.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 fsm = StateMachine.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 fsm = StateMachine.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 stateMachine = StateMachine.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.builder(NAME) + .initialState(A) + .addState(Rule.from(A).to(B)) + .build() + .checkState(B); + } + + @Test + public void testDoInStateMatches() { + control.replay(); + + StateMachine stateMachine = StateMachine.builder(NAME) + .initialState(A) + .addState(Rule.from(A).to(B)) + .build(); + + int amount = stateMachine.doInState(A, new Supplier() { + @Override public Integer get() { + return 42; + } + }); + assertThat(amount, is(42)); + + stateMachine.transition(B); + + String name = stateMachine.doInState(B, new Supplier() { + @Override public String get() { + return "jake"; + } + }); + assertThat(name, is("jake")); + } + + @Test + public void testDoInStateConcurrently() throws InterruptedException { + control.replay(); + + final StateMachine stateMachine = StateMachine.builder(NAME) + .initialState(A) + .addState(A, B) + .build(); + + final BlockingQueue results = new LinkedBlockingQueue(); + + final CountDownLatch supplier1Proceed = new CountDownLatch(1); + final ExceptionalSupplier 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 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.builder(NAME) + .initialState(A) + .addState(A, B) + .build() + .doInState(B, Commands.asSupplier(Commands.NOOP)); + } + + @Test + public void testNoThrowOnInvalidTransition() { + control.replay(); + + StateMachine machine = StateMachine.builder(NAME) + .initialState(A) + .addState(A, B) + .throwOnBadTransition(false) + .build(); + + machine.transition(C); + assertThat(machine.getState(), is(A)); + } + + private static final Clazz>> TRANSITION_CLOSURE_CLZ = + new Clazz>>() {}; + + @Test + public void testTransitionCallbacks() { + Closure> anyTransition = createMock(TRANSITION_CLOSURE_CLZ); + Closure> fromA = createMock(TRANSITION_CLOSURE_CLZ); + Closure> fromB = createMock(TRANSITION_CLOSURE_CLZ); + + Transition aToB = new Transition(A, B, true); + anyTransition.execute(aToB); + fromA.execute(aToB); + + Transition bToB = new Transition(B, B, false); + anyTransition.execute(bToB); + fromB.execute(bToB); + + Transition bToC = new Transition(B, C, true); + anyTransition.execute(bToC); + fromB.execute(bToC); + + anyTransition.execute(new Transition(C, B, true)); + + Transition bToD = new Transition(B, D, true); + anyTransition.execute(bToD); + fromB.execute(bToD); + + control.replay(); + + StateMachine machine = StateMachine.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> aToBHandler = createMock(TRANSITION_CLOSURE_CLZ); + Closure> impossibleHandler = createMock(TRANSITION_CLOSURE_CLZ); + + aToBHandler.execute(new Transition(A, B, true)); + + control.replay(); + + StateMachine machine = StateMachine.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 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 machine, String to) { + changeState(machine, to, true); + } + + private static void changeStateFail(StateMachine 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 proxyBuilder; + private Math uncachedMath; + private Math cachedMath; + private Cache intCache; + private Predicate 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 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 crazyCache = control.createMock(Cache.class); + Predicate 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 voidCache = control.createMock(Cache.class); + Predicate 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 voidCache = control.createMock(Cache.class); + Predicate 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 mathCache = control.createMock(Cache.class); + Predicate 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 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 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 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 cache = LRUCache.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 evictedKeys = Lists.newLinkedList(); + + Closure> listener = new Closure>() { + @Override public void execute(Pair evicted) { + evictedKeys.add(evicted.getFirst()); + } + }; + + LRUCache cache = LRUCache.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 callable = createMock(new Clazz>() {}); + 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 callable = createMock(new Clazz>() {}); + 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 callable = createMock(new Clazz>() {}); + 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() { + @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() { + @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()); + } +}