blob: 43ac8ce0984e7da25ca5b9039936363e4e55f0ea [file] [log] [blame]
/*
* Copyright 2012-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.util.concurrent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class MoreFuturesTest {
@Test
public void testIsSuccess() {
SettableFuture<Object> unresolvedFuture = SettableFuture.create();
assertFalse(MoreFutures.isSuccess(unresolvedFuture));
SettableFuture<Object> failedFuture = SettableFuture.create();
failedFuture.setException(new RuntimeException());
assertFalse(MoreFutures.isSuccess(failedFuture));
SettableFuture<Object> cancelledFuture = SettableFuture.create();
cancelledFuture.cancel(/* mayInterruptIfRunning */ true);
assertFalse(MoreFutures.isSuccess(cancelledFuture));
SettableFuture<Object> resolvedFuture = SettableFuture.create();
resolvedFuture.set(new Object());
assertTrue(MoreFutures.isSuccess(resolvedFuture));
}
/**
* Verifies that isSuccess will not throw InterruptedException, but will preserve the thread's
* interrupted state. Note that due to unavoidable race conditions, there is a chance that
* this test could pass even if the code were wrong. It should never spuriously fail, though.
*/
@Test
public void testInterruption() throws InterruptedException {
// Problems encountered on the background thread, which will be put into a failure message
// on the foreground thread.
final List<String> backgroundThreadProblems = Lists.newArrayList();
// Set to true after the background thread first sees that the future was successful.
final AtomicBoolean sawSuccessfulFuture = new AtomicBoolean();
// Set to true when the background thread sees that it was interrupted.
final AtomicBoolean sawInterruption = new AtomicBoolean();
// Used to spin the CPU to minimize the chance of a spurious test pass.
final AtomicInteger doNotOptimizeMe = new AtomicInteger();
final int SPIN_COUNT = 256;
// Future that the background thread will test.
// Don't make this into an immediate future, since they never throw InterruptedException.
final SettableFuture<Void> future = SettableFuture.create();
future.set(null);
Runnable testingRunner = new Runnable() {
@Override
public void run() {
try {
// Check the future once and verify that it is successful.
if (!MoreFutures.isSuccess(future)) {
backgroundThreadProblems.add("Initial check was false.");
return;
}
// Make sure we have not been interrupted yet.
if (Thread.interrupted()) {
backgroundThreadProblems.add("Was interrupted early.");
return;
}
sawSuccessfulFuture.set(true);
// Could put some sort of timeout here, but we'll leave that to the test runner.
while (true) {
// Spin some CPU to minimize the chance of interrupting after we check the future.
for (int i = 0; i < SPIN_COUNT; i++) {
doNotOptimizeMe.incrementAndGet();
}
// Let isSuccess check the interrupted flag. It should not throw.
MoreFutures.isSuccess(future);
// The old version of isSuccess could pass this test spuriously if we got interrupted
// right here. The doNotOptimizeMe loop above minimizes the chances of that.
// See if we have been interrupted, and try again if not.
if (Thread.interrupted()) {
sawInterruption.set(true);
break;
}
}
} catch (Throwable t) {
backgroundThreadProblems.add(t.toString());
}
}
};
// Start the background thread.
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(testingRunner);
// Wait for it to enter the main loop.
while (!sawSuccessfulFuture.get()) {
// Appease PMD.
sawSuccessfulFuture.get();
}
// Interrupt it and wait for it to finish.
List<Runnable> preempted = executor.shutdownNow();
executor.awaitTermination(1, TimeUnit.SECONDS);
assertTrue(sawSuccessfulFuture.get());
assertTrue(sawInterruption.get());
assertTrue(preempted.isEmpty());
if (!backgroundThreadProblems.isEmpty()) {
String msg = Joiner.on('\n').join(Iterables.concat(
ImmutableList.of("Problems on background thread:"),
backgroundThreadProblems,
ImmutableList.of("")));
fail(msg);
}
}
@Test
public void testGetFailure() {
Throwable failure = new Throwable();
ListenableFuture<Object> failedFuture = Futures.immediateFailedFuture(failure);
assertEquals(failure, MoreFutures.getFailure(failedFuture));
}
@Test(expected = NullPointerException.class)
public void testGetFailureRejectsNullFuture() {
MoreFutures.getFailure(null);
}
@Test(expected = IllegalArgumentException.class)
public void testGetFailureRequiresSatisfiedFuture() {
MoreFutures.getFailure(SettableFuture.create());
}
@Test(expected = IllegalArgumentException.class)
public void testGetFailureRequiresUnsuccessfulFuture() {
ListenableFuture<Object> success = Futures.immediateFuture(new Object());
MoreFutures.getFailure(success);
}
@Test(expected = IllegalStateException.class)
public void testGetFailureRequiresNonCancelledFuture() {
ListenableFuture<?> canceledFuture = SettableFuture.create();
canceledFuture.cancel(/* mayInterruptIfRunning */ true);
MoreFutures.getFailure(canceledFuture);
}
}