| /* |
| * Copyright (C) 2014 The Guava Authors |
| * |
| * 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.google.common.util.concurrent; |
| |
| import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; |
| |
| import com.google.common.base.CaseFormat; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.primitives.Ints; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.Locale; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.TimeUnit; |
| import junit.framework.TestCase; |
| import junit.framework.TestSuite; |
| |
| /** |
| * Generated tests for {@link Monitor}. |
| * |
| * <p>This test class generates all of its own test cases in the {@link #suite()} method. Every |
| * {@code enterXxx}, {@code tryEnterXxx}, and {@code waitForXxx} method of the {@code Monitor} class |
| * is analyzed reflectively to determine appropriate test cases based on its signature. Additional |
| * ad hoc test cases can be found in {@link SupplementalMonitorTest}. |
| * |
| * @author Justin T. Sampson |
| */ |
| |
| public class GeneratedMonitorTest extends TestCase { |
| |
| public static TestSuite suite() { |
| TestSuite suite = new TestSuite(); |
| |
| Method[] methods = Monitor.class.getMethods(); |
| sortMethods(methods); |
| for (Method method : methods) { |
| if (isAnyEnter(method) || isWaitFor(method)) { |
| validateMethod(method); |
| addTests(suite, method); |
| } |
| } |
| |
| assertEquals(548, suite.testCount()); |
| |
| return suite; |
| } |
| |
| /** A typical timeout value we'll use in the tests. */ |
| private static final long SMALL_TIMEOUT_MILLIS = 10; |
| |
| /** How long to wait when determining that a thread is blocked if we expect it to be blocked. */ |
| private static final long EXPECTED_HANG_DELAY_MILLIS = 75; |
| |
| /** |
| * How long to wait when determining that a thread is blocked if we DON'T expect it to be blocked. |
| */ |
| private static final long UNEXPECTED_HANG_DELAY_MILLIS = 10000; |
| |
| /** |
| * Various scenarios to be generated for each method under test. The actual scenario generation |
| * (determining which scenarios are applicable to which methods and what the outcome should be) |
| * takes place in {@link #addTests(TestSuite, Method)}. |
| */ |
| private enum Scenario { |
| SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING, |
| UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING, |
| SATISFIED_AND_OCCUPIED_BEFORE_ENTERING, |
| SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING, |
| |
| SATISFIED_BEFORE_WAITING, |
| SATISFIED_WHILE_WAITING, |
| SATISFIED_AND_INTERRUPTED_BEFORE_WAITING, |
| UNSATISFIED_BEFORE_AND_WHILE_WAITING, |
| UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING; |
| |
| @Override |
| public String toString() { |
| return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name()); |
| } |
| } |
| |
| /** Timeout values to combine with each {@link Scenario}. */ |
| private enum Timeout { |
| MIN(Long.MIN_VALUE, "-oo"), |
| MINUS_SMALL(-SMALL_TIMEOUT_MILLIS, "-" + SMALL_TIMEOUT_MILLIS + "ms"), |
| ZERO(0L, "0ms"), |
| SMALL(SMALL_TIMEOUT_MILLIS, SMALL_TIMEOUT_MILLIS + "ms"), |
| LARGE(UNEXPECTED_HANG_DELAY_MILLIS * 2, (2 * UNEXPECTED_HANG_DELAY_MILLIS) + "ms"), |
| MAX(Long.MAX_VALUE, "+oo"); |
| |
| final long millis; |
| final String label; |
| |
| Timeout(long millis, String label) { |
| this.millis = millis; |
| this.label = label; |
| } |
| |
| @Override |
| public String toString() { |
| return label; |
| } |
| } |
| |
| /** Convenient subsets of the {@link Timeout} enumeration for specifying scenario outcomes. */ |
| private enum TimeoutsToUse { |
| ANY(Timeout.values()), |
| PAST(Timeout.MIN, Timeout.MINUS_SMALL, Timeout.ZERO), |
| FUTURE(Timeout.SMALL, Timeout.MAX), |
| SMALL(Timeout.SMALL), |
| FINITE(Timeout.MIN, Timeout.MINUS_SMALL, Timeout.ZERO, Timeout.SMALL), |
| INFINITE(Timeout.LARGE, Timeout.MAX); |
| |
| final ImmutableList<Timeout> timeouts; |
| |
| TimeoutsToUse(Timeout... timeouts) { |
| this.timeouts = ImmutableList.copyOf(timeouts); |
| } |
| } |
| |
| /** Possible outcomes of calling any of the methods under test. */ |
| private enum Outcome { |
| |
| /** The method returned normally and is either void or returned true. */ |
| SUCCESS, |
| |
| /** The method returned false. */ |
| FAILURE, |
| |
| /** The method threw an InterruptedException. */ |
| INTERRUPT, |
| |
| /** The method did not return or throw anything. */ |
| HANG; |
| |
| @Override |
| public String toString() { |
| return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name()); |
| } |
| } |
| |
| /** Identifies all enterXxx and tryEnterXxx methods. */ |
| private static boolean isAnyEnter(Method method) { |
| return method.getName().startsWith("enter") || method.getName().startsWith("tryEnter"); |
| } |
| |
| /** Identifies just tryEnterXxx methods (a subset of {@link #isAnyEnter}), which never block. */ |
| private static boolean isTryEnter(Method method) { |
| return method.getName().startsWith("tryEnter"); |
| } |
| |
| /** |
| * Identifies just enterIfXxx methods (a subset of {@link #isAnyEnter}), which are mostly like the |
| * enterXxx methods but behave like tryEnterXxx in some scenarios. |
| */ |
| private static boolean isEnterIf(Method method) { |
| return method.getName().startsWith("enterIf"); |
| } |
| |
| /** Identifies all waitForXxx methods, which must be called while occupying the monitor. */ |
| private static boolean isWaitFor(Method method) { |
| return method.getName().startsWith("waitFor"); |
| } |
| |
| /** Determines whether the given method takes a Guard as its first parameter. */ |
| private static boolean isGuarded(Method method) { |
| Class<?>[] parameterTypes = method.getParameterTypes(); |
| return parameterTypes.length >= 1 && parameterTypes[0] == Monitor.Guard.class; |
| } |
| |
| /** Determines whether the given method takes a time and unit as its last two parameters. */ |
| private static boolean isTimed(Method method) { |
| Class<?>[] parameterTypes = method.getParameterTypes(); |
| return parameterTypes.length >= 2 |
| && parameterTypes[parameterTypes.length - 2] == long.class |
| && parameterTypes[parameterTypes.length - 1] == TimeUnit.class; |
| } |
| |
| /** Determines whether the given method returns a boolean value. */ |
| private static boolean isBoolean(Method method) { |
| return method.getReturnType() == boolean.class; |
| } |
| |
| /** Determines whether the given method can throw InterruptedException. */ |
| private static boolean isInterruptible(Method method) { |
| return Arrays.asList(method.getExceptionTypes()).contains(InterruptedException.class); |
| } |
| |
| /** Sorts the given methods primarily by name and secondarily by number of parameters. */ |
| private static void sortMethods(Method[] methods) { |
| Arrays.sort( |
| methods, |
| new Comparator<Method>() { |
| @Override |
| public int compare(Method m1, Method m2) { |
| int nameComparison = m1.getName().compareTo(m2.getName()); |
| if (nameComparison != 0) { |
| return nameComparison; |
| } else { |
| return Ints.compare(m1.getParameterTypes().length, m2.getParameterTypes().length); |
| } |
| } |
| }); |
| } |
| |
| /** Validates that the given method's signature meets all of our assumptions. */ |
| private static void validateMethod(Method method) { |
| String desc = method.toString(); |
| |
| assertTrue(desc, isAnyEnter(method) || isWaitFor(method)); |
| |
| switch (method.getParameterTypes().length) { |
| case 0: |
| assertFalse(desc, isGuarded(method)); |
| assertFalse(desc, isTimed(method)); |
| break; |
| case 1: |
| assertTrue(desc, isGuarded(method)); |
| assertFalse(desc, isTimed(method)); |
| break; |
| case 2: |
| assertFalse(desc, isGuarded(method)); |
| assertTrue(desc, isTimed(method)); |
| break; |
| case 3: |
| assertTrue(desc, isGuarded(method)); |
| assertTrue(desc, isTimed(method)); |
| break; |
| default: |
| fail(desc); |
| } |
| |
| if (method.getReturnType() == void.class) { |
| assertFalse(desc, isBoolean(method)); |
| } else { |
| assertTrue(desc, isBoolean(method)); |
| } |
| |
| switch (method.getExceptionTypes().length) { |
| case 0: |
| assertFalse(desc, isInterruptible(method)); |
| break; |
| case 1: |
| assertTrue(desc, isInterruptible(method)); |
| break; |
| default: |
| fail(desc); |
| } |
| |
| if (isEnterIf(method)) { |
| assertTrue(desc, isGuarded(method)); |
| assertTrue(desc, isBoolean(method)); |
| } else if (isTryEnter(method)) { |
| assertFalse(desc, isTimed(method)); |
| assertTrue(desc, isBoolean(method)); |
| assertFalse(desc, isInterruptible(method)); |
| } else if (isWaitFor(method)) { |
| assertTrue(desc, isGuarded(method)); |
| assertEquals(desc, isTimed(method), isBoolean(method)); |
| } else { // any other enterXxx method |
| assertEquals(desc, isTimed(method), isBoolean(method)); |
| } |
| } |
| |
| /** Generates all test cases appropriate for the given method. */ |
| private static void addTests(TestSuite suite, Method method) { |
| if (isGuarded(method)) { |
| for (boolean fair1 : new boolean[] {true, false}) { |
| for (boolean fair2 : new boolean[] {true, false}) { |
| suite.addTest(generateGuardWithWrongMonitorTestCase(method, fair1, fair2)); |
| } |
| } |
| } |
| if (isAnyEnter(method)) { |
| addTests( |
| suite, |
| method, |
| Scenario.SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING, |
| TimeoutsToUse.ANY, |
| Outcome.SUCCESS); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING, |
| TimeoutsToUse.FINITE, |
| isGuarded(method) |
| ? (isBoolean(method) ? Outcome.FAILURE : Outcome.HANG) |
| : Outcome.SUCCESS); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING, |
| TimeoutsToUse.INFINITE, |
| isGuarded(method) |
| ? (isTryEnter(method) || isEnterIf(method) ? Outcome.FAILURE : Outcome.HANG) |
| : Outcome.SUCCESS); |
| addTests( |
| suite, |
| method, |
| Scenario.SATISFIED_AND_OCCUPIED_BEFORE_ENTERING, |
| TimeoutsToUse.FINITE, |
| isBoolean(method) ? Outcome.FAILURE : Outcome.HANG); |
| addTests( |
| suite, |
| method, |
| Scenario.SATISFIED_AND_OCCUPIED_BEFORE_ENTERING, |
| TimeoutsToUse.INFINITE, |
| isGuarded(method) ? Outcome.HANG : (isTryEnter(method) ? Outcome.FAILURE : Outcome.HANG)); |
| addTests( |
| suite, |
| method, |
| Scenario.SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING, |
| TimeoutsToUse.ANY, |
| isInterruptible(method) ? Outcome.INTERRUPT : Outcome.SUCCESS); |
| } else { // any waitForXxx method |
| suite.addTest(generateWaitForWhenNotOccupyingTestCase(method, true)); |
| suite.addTest(generateWaitForWhenNotOccupyingTestCase(method, false)); |
| addTests( |
| suite, method, Scenario.SATISFIED_BEFORE_WAITING, TimeoutsToUse.ANY, Outcome.SUCCESS); |
| addTests( |
| suite, method, Scenario.SATISFIED_WHILE_WAITING, TimeoutsToUse.INFINITE, Outcome.SUCCESS); |
| addTests( |
| suite, method, Scenario.SATISFIED_WHILE_WAITING, TimeoutsToUse.PAST, Outcome.FAILURE); |
| addTests( |
| suite, |
| method, |
| Scenario.SATISFIED_AND_INTERRUPTED_BEFORE_WAITING, |
| TimeoutsToUse.ANY, |
| Outcome.SUCCESS); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_BEFORE_AND_WHILE_WAITING, |
| TimeoutsToUse.FINITE, |
| Outcome.FAILURE); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_BEFORE_AND_WHILE_WAITING, |
| TimeoutsToUse.INFINITE, |
| Outcome.HANG); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING, |
| TimeoutsToUse.PAST, |
| // prefer responding to interrupt over timing out |
| isInterruptible(method) ? Outcome.INTERRUPT : Outcome.FAILURE); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING, |
| TimeoutsToUse.SMALL, |
| isInterruptible(method) ? Outcome.INTERRUPT : Outcome.FAILURE); |
| addTests( |
| suite, |
| method, |
| Scenario.UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING, |
| TimeoutsToUse.INFINITE, |
| isInterruptible(method) ? Outcome.INTERRUPT : Outcome.HANG); |
| } |
| } |
| |
| /** |
| * Generates test cases for the given combination of scenario and timeouts. For methods that take |
| * an explicit timeout value, all of the given timeoutsToUse result in individual test cases. For |
| * methods that do not take an explicit timeout value, a single test case is generated only if the |
| * implicit timeout of that method matches the given timeoutsToUse. For example, enter() is |
| * treated like enter(MAX, MILLIS) and tryEnter() is treated like enter(0, MILLIS). |
| */ |
| private static void addTests( |
| TestSuite suite, |
| Method method, |
| Scenario scenario, |
| TimeoutsToUse timeoutsToUse, |
| Outcome expectedOutcome) { |
| for (boolean fair : new boolean[] {true, false}) { |
| if (isTimed(method)) { |
| for (Timeout timeout : timeoutsToUse.timeouts) { |
| suite.addTest(new GeneratedMonitorTest(method, scenario, fair, timeout, expectedOutcome)); |
| } |
| } else { |
| Timeout implicitTimeout = (isTryEnter(method) ? Timeout.ZERO : Timeout.MAX); |
| if (timeoutsToUse.timeouts.contains(implicitTimeout)) { |
| suite.addTest(new GeneratedMonitorTest(method, scenario, fair, null, expectedOutcome)); |
| } |
| } |
| } |
| } |
| |
| /** A guard that encapsulates a simple, mutable boolean flag. */ |
| static class FlagGuard extends Monitor.Guard { |
| |
| private boolean satisfied; |
| |
| protected FlagGuard(Monitor monitor) { |
| super(monitor); |
| } |
| |
| @Override |
| public boolean isSatisfied() { |
| return satisfied; |
| } |
| |
| public void setSatisfied(boolean satisfied) { |
| this.satisfied = satisfied; |
| } |
| } |
| |
| private final Method method; |
| private final Scenario scenario; |
| private final Timeout timeout; |
| private final Outcome expectedOutcome; |
| private final Monitor monitor; |
| private final FlagGuard guard; |
| private final CountDownLatch tearDownLatch; |
| private final CountDownLatch doingCallLatch; |
| private final CountDownLatch callCompletedLatch; |
| |
| private GeneratedMonitorTest( |
| Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome) { |
| super(nameFor(method, scenario, fair, timeout, expectedOutcome)); |
| this.method = method; |
| this.scenario = scenario; |
| this.timeout = timeout; |
| this.expectedOutcome = expectedOutcome; |
| this.monitor = new Monitor(fair); |
| this.guard = new FlagGuard(monitor); |
| this.tearDownLatch = new CountDownLatch(1); |
| this.doingCallLatch = new CountDownLatch(1); |
| this.callCompletedLatch = new CountDownLatch(1); |
| } |
| |
| private static String nameFor( |
| Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome) { |
| return String.format( |
| Locale.ROOT, |
| "%s%s(%s)/%s->%s", |
| method.getName(), |
| fair ? "(fair)" : "(nonfair)", |
| (timeout == null) ? "untimed" : timeout, |
| scenario, |
| expectedOutcome); |
| } |
| |
| @Override |
| protected void runTest() throws Throwable { |
| final Runnable runChosenTest = |
| new Runnable() { |
| @Override |
| public void run() { |
| runChosenTest(); |
| } |
| }; |
| final FutureTask<Void> task = new FutureTask<>(runChosenTest, null); |
| startThread( |
| new Runnable() { |
| @Override |
| public void run() { |
| task.run(); |
| } |
| }); |
| awaitUninterruptibly(doingCallLatch); |
| long hangDelayMillis = |
| (expectedOutcome == Outcome.HANG) |
| ? EXPECTED_HANG_DELAY_MILLIS |
| : UNEXPECTED_HANG_DELAY_MILLIS; |
| boolean hung = |
| !awaitUninterruptibly(callCompletedLatch, hangDelayMillis, TimeUnit.MILLISECONDS); |
| if (hung) { |
| assertEquals(expectedOutcome, Outcome.HANG); |
| } else { |
| assertNull(task.get(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS)); |
| } |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| // We don't want to leave stray threads running after each test. At this point, every thread |
| // launched by this test is either: |
| // |
| // (a) Blocked attempting to enter the monitor. |
| // (b) Waiting for the single guard to become satisfied. |
| // (c) Occupying the monitor and awaiting the tearDownLatch. |
| // |
| // Except for (c), every thread should occupy the monitor very briefly, and every thread leaves |
| // the monitor with the guard satisfied. Therefore as soon as tearDownLatch is triggered, we |
| // should be able to enter the monitor, and then we set the guard to satisfied for the benefit |
| // of any remaining waiting threads. |
| |
| tearDownLatch.countDown(); |
| assertTrue( |
| "Monitor still occupied in tearDown()", |
| monitor.enter(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS)); |
| try { |
| guard.setSatisfied(true); |
| } finally { |
| monitor.leave(); |
| } |
| } |
| |
| private void runChosenTest() { |
| if (isAnyEnter(method)) { |
| runEnterTest(); |
| } else { |
| runWaitTest(); |
| } |
| } |
| |
| private void runEnterTest() { |
| assertFalse(Thread.currentThread().isInterrupted()); |
| assertFalse(monitor.isOccupiedByCurrentThread()); |
| |
| doEnterScenarioSetUp(); |
| |
| boolean interruptedBeforeCall = Thread.currentThread().isInterrupted(); |
| Outcome actualOutcome = doCall(); |
| boolean occupiedAfterCall = monitor.isOccupiedByCurrentThread(); |
| boolean interruptedAfterCall = Thread.currentThread().isInterrupted(); |
| |
| if (occupiedAfterCall) { |
| guard.setSatisfied(true); |
| monitor.leave(); |
| assertFalse(monitor.isOccupiedByCurrentThread()); |
| } |
| |
| assertEquals(expectedOutcome, actualOutcome); |
| assertEquals(expectedOutcome == Outcome.SUCCESS, occupiedAfterCall); |
| assertEquals( |
| interruptedBeforeCall && expectedOutcome != Outcome.INTERRUPT, interruptedAfterCall); |
| } |
| |
| private void doEnterScenarioSetUp() { |
| switch (scenario) { |
| case SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING: |
| enterSatisfyGuardAndLeaveInCurrentThread(); |
| break; |
| case UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING: |
| break; |
| case SATISFIED_AND_OCCUPIED_BEFORE_ENTERING: |
| enterSatisfyGuardAndLeaveInCurrentThread(); |
| enterAndRemainOccupyingInAnotherThread(); |
| break; |
| case SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING: |
| enterSatisfyGuardAndLeaveInCurrentThread(); |
| Thread.currentThread().interrupt(); |
| break; |
| default: |
| throw new AssertionError("unsupported scenario: " + scenario); |
| } |
| } |
| |
| private void runWaitTest() { |
| assertFalse(Thread.currentThread().isInterrupted()); |
| assertFalse(monitor.isOccupiedByCurrentThread()); |
| monitor.enter(); |
| try { |
| assertTrue(monitor.isOccupiedByCurrentThread()); |
| |
| doWaitScenarioSetUp(); |
| |
| boolean interruptedBeforeCall = Thread.currentThread().isInterrupted(); |
| Outcome actualOutcome = doCall(); |
| boolean occupiedAfterCall = monitor.isOccupiedByCurrentThread(); |
| boolean interruptedAfterCall = Thread.currentThread().isInterrupted(); |
| |
| assertEquals(expectedOutcome, actualOutcome); |
| assertTrue(occupiedAfterCall); |
| assertEquals( |
| interruptedBeforeCall && expectedOutcome != Outcome.INTERRUPT, interruptedAfterCall); |
| } finally { |
| guard.setSatisfied(true); |
| monitor.leave(); |
| assertFalse(monitor.isOccupiedByCurrentThread()); |
| } |
| } |
| |
| private void doWaitScenarioSetUp() { |
| switch (scenario) { |
| case SATISFIED_BEFORE_WAITING: |
| guard.setSatisfied(true); |
| break; |
| case SATISFIED_WHILE_WAITING: |
| guard.setSatisfied(false); |
| enterSatisfyGuardAndLeaveInAnotherThread(); // enter blocks until we call waitFor |
| break; |
| case UNSATISFIED_BEFORE_AND_WHILE_WAITING: |
| guard.setSatisfied(false); |
| break; |
| case SATISFIED_AND_INTERRUPTED_BEFORE_WAITING: |
| guard.setSatisfied(true); |
| Thread.currentThread().interrupt(); |
| break; |
| case UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING: |
| guard.setSatisfied(false); |
| Thread.currentThread().interrupt(); |
| break; |
| default: |
| throw new AssertionError("unsupported scenario: " + scenario); |
| } |
| } |
| |
| private Outcome doCall() { |
| boolean guarded = isGuarded(method); |
| boolean timed = isTimed(method); |
| Object[] arguments = new Object[(guarded ? 1 : 0) + (timed ? 2 : 0)]; |
| if (guarded) { |
| arguments[0] = guard; |
| } |
| if (timed) { |
| arguments[arguments.length - 2] = timeout.millis; |
| arguments[arguments.length - 1] = TimeUnit.MILLISECONDS; |
| } |
| try { |
| Object result; |
| doingCallLatch.countDown(); |
| try { |
| result = method.invoke(monitor, arguments); |
| } finally { |
| callCompletedLatch.countDown(); |
| } |
| if (result == null) { |
| return Outcome.SUCCESS; |
| } else if ((Boolean) result) { |
| return Outcome.SUCCESS; |
| } else { |
| return Outcome.FAILURE; |
| } |
| } catch (InvocationTargetException targetException) { |
| Throwable actualException = targetException.getTargetException(); |
| if (actualException instanceof InterruptedException) { |
| return Outcome.INTERRUPT; |
| } else { |
| throw newAssertionError("unexpected exception", targetException); |
| } |
| } catch (IllegalAccessException e) { |
| throw newAssertionError("unexpected exception", e); |
| } |
| } |
| |
| private void enterSatisfyGuardAndLeaveInCurrentThread() { |
| monitor.enter(); |
| try { |
| guard.setSatisfied(true); |
| } finally { |
| monitor.leave(); |
| } |
| } |
| |
| private void enterSatisfyGuardAndLeaveInAnotherThread() { |
| final CountDownLatch startedLatch = new CountDownLatch(1); |
| startThread( |
| new Runnable() { |
| @Override |
| public void run() { |
| startedLatch.countDown(); |
| enterSatisfyGuardAndLeaveInCurrentThread(); |
| } |
| }); |
| awaitUninterruptibly(startedLatch); |
| } |
| |
| private void enterAndRemainOccupyingInAnotherThread() { |
| final CountDownLatch enteredLatch = new CountDownLatch(1); |
| startThread( |
| new Runnable() { |
| @Override |
| public void run() { |
| monitor.enter(); |
| try { |
| enteredLatch.countDown(); |
| awaitUninterruptibly(tearDownLatch); |
| guard.setSatisfied(true); |
| } finally { |
| monitor.leave(); |
| } |
| } |
| }); |
| awaitUninterruptibly(enteredLatch); |
| } |
| |
| @CanIgnoreReturnValue |
| static Thread startThread(Runnable runnable) { |
| Thread thread = new Thread(runnable); |
| thread.setDaemon(true); |
| thread.start(); |
| return thread; |
| } |
| |
| /** |
| * Generates a test case verifying that calling any enterXxx, tryEnterXxx, or waitForXxx method |
| * with a guard that doesn't match the monitor produces an IllegalMonitorStateException. |
| */ |
| private static TestCase generateGuardWithWrongMonitorTestCase( |
| final Method method, final boolean fair1, final boolean fair2) { |
| final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms. |
| return new TestCase(method.getName() + (timed ? "(0ms)" : "()") + "/WrongMonitor->IMSE") { |
| @Override |
| protected void runTest() throws Throwable { |
| Monitor monitor1 = new Monitor(fair1); |
| Monitor monitor2 = new Monitor(fair2); |
| FlagGuard guard = new FlagGuard(monitor2); |
| Object[] arguments = |
| (timed ? new Object[] {guard, 0L, TimeUnit.MILLISECONDS} : new Object[] {guard}); |
| boolean occupyMonitor = isWaitFor(method); |
| if (occupyMonitor) { |
| // If we don't already occupy the monitor, we'll get an IMSE regardless of the guard (see |
| // generateWaitForWhenNotOccupyingTestCase). |
| monitor1.enter(); |
| } |
| try { |
| method.invoke(monitor1, arguments); |
| fail("expected IllegalMonitorStateException"); |
| } catch (InvocationTargetException e) { |
| assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass()); |
| } finally { |
| if (occupyMonitor) { |
| monitor1.leave(); |
| } |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Generates a test case verifying that calling any waitForXxx method when not occupying the |
| * monitor produces an IllegalMonitorStateException. |
| */ |
| private static TestCase generateWaitForWhenNotOccupyingTestCase( |
| final Method method, final boolean fair) { |
| final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms. |
| String testName = |
| method.getName() |
| + (fair ? "(fair)" : "(nonfair)") |
| + (timed ? "(0ms)" : "()") |
| + "/NotOccupying->IMSE"; |
| return new TestCase(testName) { |
| @Override |
| protected void runTest() throws Throwable { |
| Monitor monitor = new Monitor(fair); |
| FlagGuard guard = new FlagGuard(monitor); |
| Object[] arguments = |
| (timed ? new Object[] {guard, 0L, TimeUnit.MILLISECONDS} : new Object[] {guard}); |
| try { |
| method.invoke(monitor, arguments); |
| fail("expected IllegalMonitorStateException"); |
| } catch (InvocationTargetException e) { |
| assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass()); |
| } |
| } |
| }; |
| } |
| |
| /** Alternative to AssertionError(String, Throwable), which doesn't exist in Java 1.6 */ |
| private static AssertionError newAssertionError(String message, Throwable cause) { |
| AssertionError e = new AssertionError(message); |
| e.initCause(cause); |
| return e; |
| } |
| } |