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