• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.util.concurrent;
18 
19 import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly;
20 
21 import com.google.common.base.CaseFormat;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.primitives.Ints;
24 import com.google.errorprone.annotations.CanIgnoreReturnValue;
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.time.Duration;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Comparator;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.concurrent.CountDownLatch;
34 import java.util.concurrent.FutureTask;
35 import java.util.concurrent.TimeUnit;
36 import junit.framework.TestCase;
37 import junit.framework.TestSuite;
38 import org.checkerframework.checker.nullness.qual.Nullable;
39 
40 /**
41  * Generated tests for {@link Monitor}.
42  *
43  * <p>This test class generates all of its own test cases in the {@link #suite()} method. Every
44  * {@code enterXxx}, {@code tryEnterXxx}, and {@code waitForXxx} method of the {@code Monitor} class
45  * is analyzed reflectively to determine appropriate test cases based on its signature. Additional
46  * ad hoc test cases can be found in {@link SupplementalMonitorTest}.
47  *
48  * @author Justin T. Sampson
49  */
50 public class GeneratedMonitorTest extends TestCase {
51 
suite()52   public static TestSuite suite() {
53     TestSuite suite = new TestSuite();
54 
55     Method[] methods = Monitor.class.getMethods();
56     sortMethods(methods);
57     for (Method method : methods) {
58       if (isAnyEnter(method) || isWaitFor(method)) {
59         validateMethod(method);
60         addTests(suite, method);
61       }
62     }
63 
64     assertEquals(980, suite.testCount());
65 
66     return suite;
67   }
68 
69   /** A typical timeout value we'll use in the tests. */
70   private static final long SMALL_TIMEOUT_MILLIS = 10;
71 
72   /** How long to wait when determining that a thread is blocked if we expect it to be blocked. */
73   private static final long EXPECTED_HANG_DELAY_MILLIS = 75;
74 
75   /**
76    * How long to wait when determining that a thread is blocked if we DON'T expect it to be blocked.
77    */
78   private static final long UNEXPECTED_HANG_DELAY_MILLIS = 10000;
79 
80   /**
81    * Various scenarios to be generated for each method under test. The actual scenario generation
82    * (determining which scenarios are applicable to which methods and what the outcome should be)
83    * takes place in {@link #addTests(TestSuite, Method)}.
84    */
85   private enum Scenario {
86     SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING,
87     UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING,
88     SATISFIED_AND_OCCUPIED_BEFORE_ENTERING,
89     SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING,
90 
91     SATISFIED_BEFORE_WAITING,
92     SATISFIED_WHILE_WAITING,
93     SATISFIED_AND_INTERRUPTED_BEFORE_WAITING,
94     UNSATISFIED_BEFORE_AND_WHILE_WAITING,
95     UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING;
96 
97     @Override
toString()98     public String toString() {
99       return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
100     }
101   }
102 
103   /** Timeout values to combine with each {@link Scenario}. */
104   private enum Timeout {
105     MIN(Long.MIN_VALUE, "-oo"),
106     MINUS_SMALL(-SMALL_TIMEOUT_MILLIS, "-" + SMALL_TIMEOUT_MILLIS + "ms"),
107     ZERO(0L, "0ms"),
108     SMALL(SMALL_TIMEOUT_MILLIS, SMALL_TIMEOUT_MILLIS + "ms"),
109     LARGE(UNEXPECTED_HANG_DELAY_MILLIS * 2, (2 * UNEXPECTED_HANG_DELAY_MILLIS) + "ms"),
110     MAX(Long.MAX_VALUE, "+oo");
111 
112     final long millis;
113     final String label;
114 
Timeout(long millis, String label)115     Timeout(long millis, String label) {
116       this.millis = millis;
117       this.label = label;
118     }
119 
120     @Override
toString()121     public String toString() {
122       return label;
123     }
124   }
125 
126   /** Convenient subsets of the {@link Timeout} enumeration for specifying scenario outcomes. */
127   private enum TimeoutsToUse {
128     ANY(Timeout.values()),
129     PAST(Timeout.MIN, Timeout.MINUS_SMALL, Timeout.ZERO),
130     FUTURE(Timeout.SMALL, Timeout.MAX),
131     SMALL(Timeout.SMALL),
132     FINITE(Timeout.MIN, Timeout.MINUS_SMALL, Timeout.ZERO, Timeout.SMALL),
133     INFINITE(Timeout.LARGE, Timeout.MAX);
134 
135     final ImmutableList<Timeout> timeouts;
136 
TimeoutsToUse(Timeout... timeouts)137     TimeoutsToUse(Timeout... timeouts) {
138       this.timeouts = ImmutableList.copyOf(timeouts);
139     }
140   }
141 
142   /** Possible outcomes of calling any of the methods under test. */
143   private enum Outcome {
144 
145     /** The method returned normally and is either void or returned true. */
146     SUCCESS,
147 
148     /** The method returned false. */
149     FAILURE,
150 
151     /** The method threw an InterruptedException. */
152     INTERRUPT,
153 
154     /** The method did not return or throw anything. */
155     HANG;
156 
157     @Override
toString()158     public String toString() {
159       return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
160     }
161   }
162 
163   /** Identifies all enterXxx and tryEnterXxx methods. */
isAnyEnter(Method method)164   private static boolean isAnyEnter(Method method) {
165     return method.getName().startsWith("enter") || method.getName().startsWith("tryEnter");
166   }
167 
168   /** Identifies just tryEnterXxx methods (a subset of {@link #isAnyEnter}), which never block. */
isTryEnter(Method method)169   private static boolean isTryEnter(Method method) {
170     return method.getName().startsWith("tryEnter");
171   }
172 
173   /**
174    * Identifies just enterIfXxx methods (a subset of {@link #isAnyEnter}), which are mostly like the
175    * enterXxx methods but behave like tryEnterXxx in some scenarios.
176    */
isEnterIf(Method method)177   private static boolean isEnterIf(Method method) {
178     return method.getName().startsWith("enterIf");
179   }
180 
181   /** Identifies all waitForXxx methods, which must be called while occupying the monitor. */
isWaitFor(Method method)182   private static boolean isWaitFor(Method method) {
183     return method.getName().startsWith("waitFor");
184   }
185 
186   /** Determines whether the given method takes a Guard as its first parameter. */
isGuarded(Method method)187   private static boolean isGuarded(Method method) {
188     Class<?>[] parameterTypes = method.getParameterTypes();
189     return parameterTypes.length >= 1 && parameterTypes[0] == Monitor.Guard.class;
190   }
191 
192   /** Determines whether the given method is time-based. */
isTimed(Method method)193   private static boolean isTimed(Method method) {
194     return isLongTimeUnitBased(method) || isDurationBased(method);
195   }
196 
197   /** Determines whether the given method takes a time and unit as its last two parameters. */
isLongTimeUnitBased(Method method)198   private static boolean isLongTimeUnitBased(Method method) {
199     Class<?>[] parameterTypes = method.getParameterTypes();
200     return parameterTypes.length >= 2
201         && parameterTypes[parameterTypes.length - 2] == long.class
202         && parameterTypes[parameterTypes.length - 1] == TimeUnit.class;
203   }
204 
205   /** Determines whether the given method takes a Duration as its last parameter. */
isDurationBased(Method method)206   private static boolean isDurationBased(Method method) {
207     Class<?>[] parameterTypes = method.getParameterTypes();
208     return parameterTypes.length >= 1
209         && parameterTypes[parameterTypes.length - 1] == Duration.class;
210   }
211 
212   /** Determines whether the given method returns a boolean value. */
isBoolean(Method method)213   private static boolean isBoolean(Method method) {
214     return method.getReturnType() == boolean.class;
215   }
216 
217   /** Determines whether the given method can throw InterruptedException. */
isInterruptible(Method method)218   private static boolean isInterruptible(Method method) {
219     return Arrays.asList(method.getExceptionTypes()).contains(InterruptedException.class);
220   }
221 
222   /** Sorts the given methods primarily by name and secondarily by number of parameters. */
sortMethods(Method[] methods)223   private static void sortMethods(Method[] methods) {
224     Arrays.sort(
225         methods,
226         new Comparator<Method>() {
227           @Override
228           public int compare(Method m1, Method m2) {
229             int nameComparison = m1.getName().compareTo(m2.getName());
230             if (nameComparison != 0) {
231               return nameComparison;
232             } else {
233               return Ints.compare(m1.getParameterTypes().length, m2.getParameterTypes().length);
234             }
235           }
236         });
237   }
238 
239   /** Validates that the given method's signature meets all of our assumptions. */
validateMethod(Method method)240   private static void validateMethod(Method method) {
241     String desc = method.toString();
242 
243     assertTrue(desc, isAnyEnter(method) || isWaitFor(method));
244 
245     switch (method.getParameterTypes().length) {
246       case 0:
247         assertFalse(desc, isGuarded(method));
248         assertFalse(desc, isTimed(method));
249         break;
250       case 1:
251         if (isDurationBased(method)) {
252           assertFalse(desc, isGuarded(method));
253         } else {
254           assertTrue(desc, isGuarded(method));
255         }
256         // we can't make an assumption about isTimed() because now we have single-parameter methods
257         // that accept a java.time.Duration
258         assertFalse(desc, isLongTimeUnitBased(method));
259         break;
260       case 2:
261         if (isDurationBased(method)) {
262           assertTrue(desc, isGuarded(method));
263         } else {
264           assertFalse(desc, isGuarded(method));
265         }
266         assertTrue(desc, isTimed(method));
267         break;
268       case 3:
269         assertTrue(desc, isGuarded(method));
270         assertTrue(desc, isTimed(method));
271         break;
272       default:
273         fail(desc);
274     }
275 
276     if (method.getReturnType() == void.class) {
277       assertFalse(desc, isBoolean(method));
278     } else {
279       assertTrue(desc, isBoolean(method));
280     }
281 
282     switch (method.getExceptionTypes().length) {
283       case 0:
284         assertFalse(desc, isInterruptible(method));
285         break;
286       case 1:
287         assertTrue(desc, isInterruptible(method));
288         break;
289       default:
290         fail(desc);
291     }
292 
293     if (isEnterIf(method)) {
294       assertTrue(desc, isGuarded(method));
295       assertTrue(desc, isBoolean(method));
296     } else if (isTryEnter(method)) {
297       assertFalse(desc, isTimed(method));
298       assertTrue(desc, isBoolean(method));
299       assertFalse(desc, isInterruptible(method));
300     } else if (isWaitFor(method)) {
301       assertTrue(desc, isGuarded(method));
302       assertEquals(desc, isTimed(method), isBoolean(method));
303     } else { // any other enterXxx method
304       assertEquals(desc, isTimed(method), isBoolean(method));
305     }
306   }
307 
308   /** Generates all test cases appropriate for the given method. */
addTests(TestSuite suite, Method method)309   private static void addTests(TestSuite suite, Method method) {
310     if (isGuarded(method)) {
311       for (boolean fair1 : new boolean[] {true, false}) {
312         for (boolean fair2 : new boolean[] {true, false}) {
313           suite.addTest(generateGuardWithWrongMonitorTestCase(method, fair1, fair2));
314         }
315       }
316     }
317     if (isAnyEnter(method)) {
318       addTests(
319           suite,
320           method,
321           Scenario.SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING,
322           TimeoutsToUse.ANY,
323           Outcome.SUCCESS);
324       addTests(
325           suite,
326           method,
327           Scenario.UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING,
328           TimeoutsToUse.FINITE,
329           isGuarded(method)
330               ? (isBoolean(method) ? Outcome.FAILURE : Outcome.HANG)
331               : Outcome.SUCCESS);
332       addTests(
333           suite,
334           method,
335           Scenario.UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING,
336           TimeoutsToUse.INFINITE,
337           isGuarded(method)
338               ? (isTryEnter(method) || isEnterIf(method) ? Outcome.FAILURE : Outcome.HANG)
339               : Outcome.SUCCESS);
340       addTests(
341           suite,
342           method,
343           Scenario.SATISFIED_AND_OCCUPIED_BEFORE_ENTERING,
344           TimeoutsToUse.FINITE,
345           isBoolean(method) ? Outcome.FAILURE : Outcome.HANG);
346       addTests(
347           suite,
348           method,
349           Scenario.SATISFIED_AND_OCCUPIED_BEFORE_ENTERING,
350           TimeoutsToUse.INFINITE,
351           isGuarded(method) ? Outcome.HANG : (isTryEnter(method) ? Outcome.FAILURE : Outcome.HANG));
352       addTests(
353           suite,
354           method,
355           Scenario.SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING,
356           TimeoutsToUse.ANY,
357           isInterruptible(method) ? Outcome.INTERRUPT : Outcome.SUCCESS);
358     } else { // any waitForXxx method
359       suite.addTest(generateWaitForWhenNotOccupyingTestCase(method, true));
360       suite.addTest(generateWaitForWhenNotOccupyingTestCase(method, false));
361       addTests(
362           suite, method, Scenario.SATISFIED_BEFORE_WAITING, TimeoutsToUse.ANY, Outcome.SUCCESS);
363       addTests(
364           suite, method, Scenario.SATISFIED_WHILE_WAITING, TimeoutsToUse.INFINITE, Outcome.SUCCESS);
365       addTests(
366           suite, method, Scenario.SATISFIED_WHILE_WAITING, TimeoutsToUse.PAST, Outcome.FAILURE);
367       addTests(
368           suite,
369           method,
370           Scenario.SATISFIED_AND_INTERRUPTED_BEFORE_WAITING,
371           TimeoutsToUse.ANY,
372           Outcome.SUCCESS);
373       addTests(
374           suite,
375           method,
376           Scenario.UNSATISFIED_BEFORE_AND_WHILE_WAITING,
377           TimeoutsToUse.FINITE,
378           Outcome.FAILURE);
379       addTests(
380           suite,
381           method,
382           Scenario.UNSATISFIED_BEFORE_AND_WHILE_WAITING,
383           TimeoutsToUse.INFINITE,
384           Outcome.HANG);
385       addTests(
386           suite,
387           method,
388           Scenario.UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING,
389           TimeoutsToUse.PAST,
390           // prefer responding to interrupt over timing out
391           isInterruptible(method) ? Outcome.INTERRUPT : Outcome.FAILURE);
392       addTests(
393           suite,
394           method,
395           Scenario.UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING,
396           TimeoutsToUse.SMALL,
397           isInterruptible(method) ? Outcome.INTERRUPT : Outcome.FAILURE);
398       addTests(
399           suite,
400           method,
401           Scenario.UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING,
402           TimeoutsToUse.INFINITE,
403           isInterruptible(method) ? Outcome.INTERRUPT : Outcome.HANG);
404     }
405   }
406 
407   /**
408    * Generates test cases for the given combination of scenario and timeouts. For methods that take
409    * an explicit timeout value, all of the given timeoutsToUse result in individual test cases. For
410    * methods that do not take an explicit timeout value, a single test case is generated only if the
411    * implicit timeout of that method matches the given timeoutsToUse. For example, enter() is
412    * treated like enter(MAX, MILLIS) and tryEnter() is treated like enter(0, MILLIS).
413    */
addTests( TestSuite suite, Method method, Scenario scenario, TimeoutsToUse timeoutsToUse, Outcome expectedOutcome)414   private static void addTests(
415       TestSuite suite,
416       Method method,
417       Scenario scenario,
418       TimeoutsToUse timeoutsToUse,
419       Outcome expectedOutcome) {
420     for (boolean fair : new boolean[] {true, false}) {
421       if (isTimed(method)) {
422         for (Timeout timeout : timeoutsToUse.timeouts) {
423           suite.addTest(new GeneratedMonitorTest(method, scenario, fair, timeout, expectedOutcome));
424         }
425       } else {
426         Timeout implicitTimeout = (isTryEnter(method) ? Timeout.ZERO : Timeout.MAX);
427         if (timeoutsToUse.timeouts.contains(implicitTimeout)) {
428           suite.addTest(new GeneratedMonitorTest(method, scenario, fair, null, expectedOutcome));
429         }
430       }
431     }
432   }
433 
434   /** A guard that encapsulates a simple, mutable boolean flag. */
435   static class FlagGuard extends Monitor.Guard {
436 
437     private boolean satisfied;
438 
FlagGuard(Monitor monitor)439     protected FlagGuard(Monitor monitor) {
440       super(monitor);
441     }
442 
443     @Override
isSatisfied()444     public boolean isSatisfied() {
445       return satisfied;
446     }
447 
setSatisfied(boolean satisfied)448     public void setSatisfied(boolean satisfied) {
449       this.satisfied = satisfied;
450     }
451   }
452 
453   private final Method method;
454   private final Scenario scenario;
455   private final Timeout timeout;
456   private final Outcome expectedOutcome;
457   private final Monitor monitor;
458   private final FlagGuard guard;
459   private final CountDownLatch tearDownLatch;
460   private final CountDownLatch doingCallLatch;
461   private final CountDownLatch callCompletedLatch;
462 
GeneratedMonitorTest( Method method, Scenario scenario, boolean fair, @Nullable Timeout timeout, Outcome expectedOutcome)463   private GeneratedMonitorTest(
464       Method method,
465       Scenario scenario,
466       boolean fair,
467       @Nullable Timeout timeout,
468       Outcome expectedOutcome) {
469     super(nameFor(method, scenario, fair, timeout, expectedOutcome));
470     this.method = method;
471     this.scenario = scenario;
472     this.timeout = timeout;
473     this.expectedOutcome = expectedOutcome;
474     this.monitor = new Monitor(fair);
475     this.guard = new FlagGuard(monitor);
476     this.tearDownLatch = new CountDownLatch(1);
477     this.doingCallLatch = new CountDownLatch(1);
478     this.callCompletedLatch = new CountDownLatch(1);
479   }
480 
nameFor( Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome)481   private static String nameFor(
482       Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome) {
483     return String.format(
484         Locale.ROOT,
485         "%s%s(%s)/%s->%s",
486         method.getName(),
487         fair ? "(fair)" : "(nonfair)",
488         (timeout == null) ? "untimed" : timeout,
489         scenario,
490         expectedOutcome);
491   }
492 
493   @Override
runTest()494   protected void runTest() throws Throwable {
495     final Runnable runChosenTest =
496         new Runnable() {
497           @Override
498           public void run() {
499             runChosenTest();
500           }
501         };
502     final FutureTask<@Nullable Void> task = new FutureTask<>(runChosenTest, null);
503     startThread(
504         new Runnable() {
505           @Override
506           public void run() {
507             task.run();
508           }
509         });
510     awaitUninterruptibly(doingCallLatch);
511     long hangDelayMillis =
512         (expectedOutcome == Outcome.HANG)
513             ? EXPECTED_HANG_DELAY_MILLIS
514             : UNEXPECTED_HANG_DELAY_MILLIS;
515     boolean hung =
516         !awaitUninterruptibly(callCompletedLatch, hangDelayMillis, TimeUnit.MILLISECONDS);
517     if (hung) {
518       assertEquals(expectedOutcome, Outcome.HANG);
519     } else {
520       assertNull(task.get(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS));
521     }
522   }
523 
524   @Override
tearDown()525   protected void tearDown() throws Exception {
526     // We don't want to leave stray threads running after each test. At this point, every thread
527     // launched by this test is either:
528     //
529     // (a) Blocked attempting to enter the monitor.
530     // (b) Waiting for the single guard to become satisfied.
531     // (c) Occupying the monitor and awaiting the tearDownLatch.
532     //
533     // Except for (c), every thread should occupy the monitor very briefly, and every thread leaves
534     // the monitor with the guard satisfied. Therefore as soon as tearDownLatch is triggered, we
535     // should be able to enter the monitor, and then we set the guard to satisfied for the benefit
536     // of any remaining waiting threads.
537 
538     tearDownLatch.countDown();
539     assertTrue(
540         "Monitor still occupied in tearDown()",
541         monitor.enter(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS));
542     try {
543       guard.setSatisfied(true);
544     } finally {
545       monitor.leave();
546     }
547   }
548 
runChosenTest()549   private void runChosenTest() {
550     if (isAnyEnter(method)) {
551       runEnterTest();
552     } else {
553       runWaitTest();
554     }
555   }
556 
runEnterTest()557   private void runEnterTest() {
558     assertFalse(Thread.currentThread().isInterrupted());
559     assertFalse(monitor.isOccupiedByCurrentThread());
560 
561     doEnterScenarioSetUp();
562 
563     boolean interruptedBeforeCall = Thread.currentThread().isInterrupted();
564     Outcome actualOutcome = doCall();
565     boolean occupiedAfterCall = monitor.isOccupiedByCurrentThread();
566     boolean interruptedAfterCall = Thread.currentThread().isInterrupted();
567 
568     if (occupiedAfterCall) {
569       guard.setSatisfied(true);
570       monitor.leave();
571       assertFalse(monitor.isOccupiedByCurrentThread());
572     }
573 
574     assertEquals(expectedOutcome, actualOutcome);
575     assertEquals(expectedOutcome == Outcome.SUCCESS, occupiedAfterCall);
576     assertEquals(
577         interruptedBeforeCall && expectedOutcome != Outcome.INTERRUPT, interruptedAfterCall);
578   }
579 
doEnterScenarioSetUp()580   private void doEnterScenarioSetUp() {
581     switch (scenario) {
582       case SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING:
583         enterSatisfyGuardAndLeaveInCurrentThread();
584         break;
585       case UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING:
586         break;
587       case SATISFIED_AND_OCCUPIED_BEFORE_ENTERING:
588         enterSatisfyGuardAndLeaveInCurrentThread();
589         enterAndRemainOccupyingInAnotherThread();
590         break;
591       case SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING:
592         enterSatisfyGuardAndLeaveInCurrentThread();
593         Thread.currentThread().interrupt();
594         break;
595       default:
596         throw new AssertionError("unsupported scenario: " + scenario);
597     }
598   }
599 
runWaitTest()600   private void runWaitTest() {
601     assertFalse(Thread.currentThread().isInterrupted());
602     assertFalse(monitor.isOccupiedByCurrentThread());
603     monitor.enter();
604     try {
605       assertTrue(monitor.isOccupiedByCurrentThread());
606 
607       doWaitScenarioSetUp();
608 
609       boolean interruptedBeforeCall = Thread.currentThread().isInterrupted();
610       Outcome actualOutcome = doCall();
611       boolean occupiedAfterCall = monitor.isOccupiedByCurrentThread();
612       boolean interruptedAfterCall = Thread.currentThread().isInterrupted();
613 
614       assertEquals(expectedOutcome, actualOutcome);
615       assertTrue(occupiedAfterCall);
616       assertEquals(
617           interruptedBeforeCall && expectedOutcome != Outcome.INTERRUPT, interruptedAfterCall);
618     } finally {
619       guard.setSatisfied(true);
620       monitor.leave();
621       assertFalse(monitor.isOccupiedByCurrentThread());
622     }
623   }
624 
doWaitScenarioSetUp()625   private void doWaitScenarioSetUp() {
626     switch (scenario) {
627       case SATISFIED_BEFORE_WAITING:
628         guard.setSatisfied(true);
629         break;
630       case SATISFIED_WHILE_WAITING:
631         guard.setSatisfied(false);
632         enterSatisfyGuardAndLeaveInAnotherThread(); // enter blocks until we call waitFor
633         break;
634       case UNSATISFIED_BEFORE_AND_WHILE_WAITING:
635         guard.setSatisfied(false);
636         break;
637       case SATISFIED_AND_INTERRUPTED_BEFORE_WAITING:
638         guard.setSatisfied(true);
639         Thread.currentThread().interrupt();
640         break;
641       case UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING:
642         guard.setSatisfied(false);
643         Thread.currentThread().interrupt();
644         break;
645       default:
646         throw new AssertionError("unsupported scenario: " + scenario);
647     }
648   }
649 
doCall()650   private Outcome doCall() {
651     List<Object> arguments = new ArrayList<>();
652     if (isGuarded(method)) {
653       arguments.add(guard);
654     }
655     if (isLongTimeUnitBased(method)) {
656       arguments.add(timeout.millis);
657       arguments.add(TimeUnit.MILLISECONDS);
658     }
659     if (isDurationBased(method)) {
660       arguments.add(Duration.ofMillis(timeout.millis));
661     }
662     try {
663       Object result;
664       doingCallLatch.countDown();
665       try {
666         result = method.invoke(monitor, arguments.toArray());
667       } finally {
668         callCompletedLatch.countDown();
669       }
670       if (result == null) {
671         return Outcome.SUCCESS;
672       } else if ((Boolean) result) {
673         return Outcome.SUCCESS;
674       } else {
675         return Outcome.FAILURE;
676       }
677     } catch (InvocationTargetException targetException) {
678       Throwable actualException = targetException.getTargetException();
679       if (actualException instanceof InterruptedException) {
680         return Outcome.INTERRUPT;
681       } else {
682         throw newAssertionError("unexpected exception", targetException);
683       }
684     } catch (IllegalAccessException e) {
685       throw newAssertionError("unexpected exception", e);
686     }
687   }
688 
enterSatisfyGuardAndLeaveInCurrentThread()689   private void enterSatisfyGuardAndLeaveInCurrentThread() {
690     monitor.enter();
691     try {
692       guard.setSatisfied(true);
693     } finally {
694       monitor.leave();
695     }
696   }
697 
enterSatisfyGuardAndLeaveInAnotherThread()698   private void enterSatisfyGuardAndLeaveInAnotherThread() {
699     final CountDownLatch startedLatch = new CountDownLatch(1);
700     startThread(
701         new Runnable() {
702           @Override
703           public void run() {
704             startedLatch.countDown();
705             enterSatisfyGuardAndLeaveInCurrentThread();
706           }
707         });
708     awaitUninterruptibly(startedLatch);
709   }
710 
enterAndRemainOccupyingInAnotherThread()711   private void enterAndRemainOccupyingInAnotherThread() {
712     final CountDownLatch enteredLatch = new CountDownLatch(1);
713     startThread(
714         new Runnable() {
715           @Override
716           public void run() {
717             monitor.enter();
718             try {
719               enteredLatch.countDown();
720               awaitUninterruptibly(tearDownLatch);
721               guard.setSatisfied(true);
722             } finally {
723               monitor.leave();
724             }
725           }
726         });
727     awaitUninterruptibly(enteredLatch);
728   }
729 
730   @CanIgnoreReturnValue
startThread(Runnable runnable)731   static Thread startThread(Runnable runnable) {
732     Thread thread = new Thread(runnable);
733     thread.setDaemon(true);
734     thread.start();
735     return thread;
736   }
737 
738   /**
739    * Generates a test case verifying that calling any enterXxx, tryEnterXxx, or waitForXxx method
740    * with a guard that doesn't match the monitor produces an IllegalMonitorStateException.
741    */
generateGuardWithWrongMonitorTestCase( final Method method, final boolean fair1, final boolean fair2)742   private static TestCase generateGuardWithWrongMonitorTestCase(
743       final Method method, final boolean fair1, final boolean fair2) {
744     final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms.
745     return new TestCase(method.getName() + (timed ? "(0ms)" : "()") + "/WrongMonitor->IMSE") {
746       @Override
747       protected void runTest() throws Throwable {
748         Monitor monitor1 = new Monitor(fair1);
749         Monitor monitor2 = new Monitor(fair2);
750         FlagGuard guard = new FlagGuard(monitor2);
751         List<Object> arguments = new ArrayList<>();
752         arguments.add(guard);
753         if (isDurationBased(method)) {
754           arguments.add(Duration.ZERO);
755         }
756         if (isLongTimeUnitBased(method)) {
757           arguments.add(0L);
758           arguments.add(TimeUnit.MILLISECONDS);
759         }
760         boolean occupyMonitor = isWaitFor(method);
761         if (occupyMonitor) {
762           // If we don't already occupy the monitor, we'll get an IMSE regardless of the guard (see
763           // generateWaitForWhenNotOccupyingTestCase).
764           monitor1.enter();
765         }
766         try {
767           method.invoke(monitor1, arguments.toArray());
768           fail("expected IllegalMonitorStateException");
769         } catch (InvocationTargetException e) {
770           assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass());
771         } finally {
772           if (occupyMonitor) {
773             monitor1.leave();
774           }
775         }
776       }
777     };
778   }
779 
780   /**
781    * Generates a test case verifying that calling any waitForXxx method when not occupying the
782    * monitor produces an IllegalMonitorStateException.
783    */
784   private static TestCase generateWaitForWhenNotOccupyingTestCase(
785       final Method method, final boolean fair) {
786     final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms.
787     String testName =
788         method.getName()
789             + (fair ? "(fair)" : "(nonfair)")
790             + (timed ? "(0ms)" : "()")
791             + "/NotOccupying->IMSE";
792     return new TestCase(testName) {
793       @Override
794       protected void runTest() throws Throwable {
795         Monitor monitor = new Monitor(fair);
796         FlagGuard guard = new FlagGuard(monitor);
797         List<Object> arguments = new ArrayList<>();
798         arguments.add(guard);
799         if (isDurationBased(method)) {
800           arguments.add(Duration.ZERO);
801         }
802         if (isLongTimeUnitBased(method)) {
803           arguments.add(0L);
804           arguments.add(TimeUnit.MILLISECONDS);
805         }
806         try {
807           method.invoke(monitor, arguments.toArray());
808           fail("expected IllegalMonitorStateException");
809         } catch (InvocationTargetException e) {
810           assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass());
811         }
812       }
813     };
814   }
815 
816   /** Alternative to AssertionError(String, Throwable), which doesn't exist in Java 1.6 */
817   private static AssertionError newAssertionError(String message, Throwable cause) {
818     AssertionError e = new AssertionError(message);
819     e.initCause(cause);
820     return e;
821   }
822 }
823