| /* |
| * 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); |
| } |
| } |