blob: 872197be8b91ee61988ca0380159a74eddee48bd [file] [log] [blame]
/*
* 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;
}
}