• 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 
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 
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, Timeout timeout, Outcome expectedOutcome)463   private GeneratedMonitorTest(
464       Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome) {
465     super(nameFor(method, scenario, fair, timeout, expectedOutcome));
466     this.method = method;
467     this.scenario = scenario;
468     this.timeout = timeout;
469     this.expectedOutcome = expectedOutcome;
470     this.monitor = new Monitor(fair);
471     this.guard = new FlagGuard(monitor);
472     this.tearDownLatch = new CountDownLatch(1);
473     this.doingCallLatch = new CountDownLatch(1);
474     this.callCompletedLatch = new CountDownLatch(1);
475   }
476 
nameFor( Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome)477   private static String nameFor(
478       Method method, Scenario scenario, boolean fair, Timeout timeout, Outcome expectedOutcome) {
479     return String.format(
480         Locale.ROOT,
481         "%s%s(%s)/%s->%s",
482         method.getName(),
483         fair ? "(fair)" : "(nonfair)",
484         (timeout == null) ? "untimed" : timeout,
485         scenario,
486         expectedOutcome);
487   }
488 
489   @Override
runTest()490   protected void runTest() throws Throwable {
491     final Runnable runChosenTest =
492         new Runnable() {
493           @Override
494           public void run() {
495             runChosenTest();
496           }
497         };
498     final FutureTask<Void> task = new FutureTask<>(runChosenTest, null);
499     startThread(
500         new Runnable() {
501           @Override
502           public void run() {
503             task.run();
504           }
505         });
506     awaitUninterruptibly(doingCallLatch);
507     long hangDelayMillis =
508         (expectedOutcome == Outcome.HANG)
509             ? EXPECTED_HANG_DELAY_MILLIS
510             : UNEXPECTED_HANG_DELAY_MILLIS;
511     boolean hung =
512         !awaitUninterruptibly(callCompletedLatch, hangDelayMillis, TimeUnit.MILLISECONDS);
513     if (hung) {
514       assertEquals(expectedOutcome, Outcome.HANG);
515     } else {
516       assertNull(task.get(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS));
517     }
518   }
519 
520   @Override
tearDown()521   protected void tearDown() throws Exception {
522     // We don't want to leave stray threads running after each test. At this point, every thread
523     // launched by this test is either:
524     //
525     // (a) Blocked attempting to enter the monitor.
526     // (b) Waiting for the single guard to become satisfied.
527     // (c) Occupying the monitor and awaiting the tearDownLatch.
528     //
529     // Except for (c), every thread should occupy the monitor very briefly, and every thread leaves
530     // the monitor with the guard satisfied. Therefore as soon as tearDownLatch is triggered, we
531     // should be able to enter the monitor, and then we set the guard to satisfied for the benefit
532     // of any remaining waiting threads.
533 
534     tearDownLatch.countDown();
535     assertTrue(
536         "Monitor still occupied in tearDown()",
537         monitor.enter(UNEXPECTED_HANG_DELAY_MILLIS, TimeUnit.MILLISECONDS));
538     try {
539       guard.setSatisfied(true);
540     } finally {
541       monitor.leave();
542     }
543   }
544 
runChosenTest()545   private void runChosenTest() {
546     if (isAnyEnter(method)) {
547       runEnterTest();
548     } else {
549       runWaitTest();
550     }
551   }
552 
runEnterTest()553   private void runEnterTest() {
554     assertFalse(Thread.currentThread().isInterrupted());
555     assertFalse(monitor.isOccupiedByCurrentThread());
556 
557     doEnterScenarioSetUp();
558 
559     boolean interruptedBeforeCall = Thread.currentThread().isInterrupted();
560     Outcome actualOutcome = doCall();
561     boolean occupiedAfterCall = monitor.isOccupiedByCurrentThread();
562     boolean interruptedAfterCall = Thread.currentThread().isInterrupted();
563 
564     if (occupiedAfterCall) {
565       guard.setSatisfied(true);
566       monitor.leave();
567       assertFalse(monitor.isOccupiedByCurrentThread());
568     }
569 
570     assertEquals(expectedOutcome, actualOutcome);
571     assertEquals(expectedOutcome == Outcome.SUCCESS, occupiedAfterCall);
572     assertEquals(
573         interruptedBeforeCall && expectedOutcome != Outcome.INTERRUPT, interruptedAfterCall);
574   }
575 
doEnterScenarioSetUp()576   private void doEnterScenarioSetUp() {
577     switch (scenario) {
578       case SATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING:
579         enterSatisfyGuardAndLeaveInCurrentThread();
580         break;
581       case UNSATISFIED_AND_UNOCCUPIED_BEFORE_ENTERING:
582         break;
583       case SATISFIED_AND_OCCUPIED_BEFORE_ENTERING:
584         enterSatisfyGuardAndLeaveInCurrentThread();
585         enterAndRemainOccupyingInAnotherThread();
586         break;
587       case SATISFIED_UNOCCUPIED_AND_INTERRUPTED_BEFORE_ENTERING:
588         enterSatisfyGuardAndLeaveInCurrentThread();
589         Thread.currentThread().interrupt();
590         break;
591       default:
592         throw new AssertionError("unsupported scenario: " + scenario);
593     }
594   }
595 
runWaitTest()596   private void runWaitTest() {
597     assertFalse(Thread.currentThread().isInterrupted());
598     assertFalse(monitor.isOccupiedByCurrentThread());
599     monitor.enter();
600     try {
601       assertTrue(monitor.isOccupiedByCurrentThread());
602 
603       doWaitScenarioSetUp();
604 
605       boolean interruptedBeforeCall = Thread.currentThread().isInterrupted();
606       Outcome actualOutcome = doCall();
607       boolean occupiedAfterCall = monitor.isOccupiedByCurrentThread();
608       boolean interruptedAfterCall = Thread.currentThread().isInterrupted();
609 
610       assertEquals(expectedOutcome, actualOutcome);
611       assertTrue(occupiedAfterCall);
612       assertEquals(
613           interruptedBeforeCall && expectedOutcome != Outcome.INTERRUPT, interruptedAfterCall);
614     } finally {
615       guard.setSatisfied(true);
616       monitor.leave();
617       assertFalse(monitor.isOccupiedByCurrentThread());
618     }
619   }
620 
doWaitScenarioSetUp()621   private void doWaitScenarioSetUp() {
622     switch (scenario) {
623       case SATISFIED_BEFORE_WAITING:
624         guard.setSatisfied(true);
625         break;
626       case SATISFIED_WHILE_WAITING:
627         guard.setSatisfied(false);
628         enterSatisfyGuardAndLeaveInAnotherThread(); // enter blocks until we call waitFor
629         break;
630       case UNSATISFIED_BEFORE_AND_WHILE_WAITING:
631         guard.setSatisfied(false);
632         break;
633       case SATISFIED_AND_INTERRUPTED_BEFORE_WAITING:
634         guard.setSatisfied(true);
635         Thread.currentThread().interrupt();
636         break;
637       case UNSATISFIED_AND_INTERRUPTED_BEFORE_WAITING:
638         guard.setSatisfied(false);
639         Thread.currentThread().interrupt();
640         break;
641       default:
642         throw new AssertionError("unsupported scenario: " + scenario);
643     }
644   }
645 
doCall()646   private Outcome doCall() {
647     List<Object> arguments = new ArrayList<>();
648     if (isGuarded(method)) {
649       arguments.add(guard);
650     }
651     if (isLongTimeUnitBased(method)) {
652       arguments.add(timeout.millis);
653       arguments.add(TimeUnit.MILLISECONDS);
654     }
655     if (isDurationBased(method)) {
656       arguments.add(Duration.ofMillis(timeout.millis));
657     }
658     try {
659       Object result;
660       doingCallLatch.countDown();
661       try {
662         result = method.invoke(monitor, arguments.toArray());
663       } finally {
664         callCompletedLatch.countDown();
665       }
666       if (result == null) {
667         return Outcome.SUCCESS;
668       } else if ((Boolean) result) {
669         return Outcome.SUCCESS;
670       } else {
671         return Outcome.FAILURE;
672       }
673     } catch (InvocationTargetException targetException) {
674       Throwable actualException = targetException.getTargetException();
675       if (actualException instanceof InterruptedException) {
676         return Outcome.INTERRUPT;
677       } else {
678         throw newAssertionError("unexpected exception", targetException);
679       }
680     } catch (IllegalAccessException e) {
681       throw newAssertionError("unexpected exception", e);
682     }
683   }
684 
enterSatisfyGuardAndLeaveInCurrentThread()685   private void enterSatisfyGuardAndLeaveInCurrentThread() {
686     monitor.enter();
687     try {
688       guard.setSatisfied(true);
689     } finally {
690       monitor.leave();
691     }
692   }
693 
enterSatisfyGuardAndLeaveInAnotherThread()694   private void enterSatisfyGuardAndLeaveInAnotherThread() {
695     final CountDownLatch startedLatch = new CountDownLatch(1);
696     startThread(
697         new Runnable() {
698           @Override
699           public void run() {
700             startedLatch.countDown();
701             enterSatisfyGuardAndLeaveInCurrentThread();
702           }
703         });
704     awaitUninterruptibly(startedLatch);
705   }
706 
enterAndRemainOccupyingInAnotherThread()707   private void enterAndRemainOccupyingInAnotherThread() {
708     final CountDownLatch enteredLatch = new CountDownLatch(1);
709     startThread(
710         new Runnable() {
711           @Override
712           public void run() {
713             monitor.enter();
714             try {
715               enteredLatch.countDown();
716               awaitUninterruptibly(tearDownLatch);
717               guard.setSatisfied(true);
718             } finally {
719               monitor.leave();
720             }
721           }
722         });
723     awaitUninterruptibly(enteredLatch);
724   }
725 
726   @CanIgnoreReturnValue
startThread(Runnable runnable)727   static Thread startThread(Runnable runnable) {
728     Thread thread = new Thread(runnable);
729     thread.setDaemon(true);
730     thread.start();
731     return thread;
732   }
733 
734   /**
735    * Generates a test case verifying that calling any enterXxx, tryEnterXxx, or waitForXxx method
736    * with a guard that doesn't match the monitor produces an IllegalMonitorStateException.
737    */
generateGuardWithWrongMonitorTestCase( final Method method, final boolean fair1, final boolean fair2)738   private static TestCase generateGuardWithWrongMonitorTestCase(
739       final Method method, final boolean fair1, final boolean fair2) {
740     final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms.
741     return new TestCase(method.getName() + (timed ? "(0ms)" : "()") + "/WrongMonitor->IMSE") {
742       @Override
743       protected void runTest() throws Throwable {
744         Monitor monitor1 = new Monitor(fair1);
745         Monitor monitor2 = new Monitor(fair2);
746         FlagGuard guard = new FlagGuard(monitor2);
747         List<Object> arguments = new ArrayList<>();
748         arguments.add(guard);
749         if (isDurationBased(method)) {
750           arguments.add(Duration.ZERO);
751         }
752         if (isLongTimeUnitBased(method)) {
753           arguments.add(0L);
754           arguments.add(TimeUnit.MILLISECONDS);
755         }
756         boolean occupyMonitor = isWaitFor(method);
757         if (occupyMonitor) {
758           // If we don't already occupy the monitor, we'll get an IMSE regardless of the guard (see
759           // generateWaitForWhenNotOccupyingTestCase).
760           monitor1.enter();
761         }
762         try {
763           method.invoke(monitor1, arguments.toArray());
764           fail("expected IllegalMonitorStateException");
765         } catch (InvocationTargetException e) {
766           assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass());
767         } finally {
768           if (occupyMonitor) {
769             monitor1.leave();
770           }
771         }
772       }
773     };
774   }
775 
776   /**
777    * Generates a test case verifying that calling any waitForXxx method when not occupying the
778    * monitor produces an IllegalMonitorStateException.
779    */
780   private static TestCase generateWaitForWhenNotOccupyingTestCase(
781       final Method method, final boolean fair) {
782     final boolean timed = isTimed(method); // Not going to bother with all timeouts, just 0ms.
783     String testName =
784         method.getName()
785             + (fair ? "(fair)" : "(nonfair)")
786             + (timed ? "(0ms)" : "()")
787             + "/NotOccupying->IMSE";
788     return new TestCase(testName) {
789       @Override
790       protected void runTest() throws Throwable {
791         Monitor monitor = new Monitor(fair);
792         FlagGuard guard = new FlagGuard(monitor);
793         List<Object> arguments = new ArrayList<>();
794         arguments.add(guard);
795         if (isDurationBased(method)) {
796           arguments.add(Duration.ZERO);
797         }
798         if (isLongTimeUnitBased(method)) {
799           arguments.add(0L);
800           arguments.add(TimeUnit.MILLISECONDS);
801         }
802         try {
803           method.invoke(monitor, arguments.toArray());
804           fail("expected IllegalMonitorStateException");
805         } catch (InvocationTargetException e) {
806           assertEquals(IllegalMonitorStateException.class, e.getTargetException().getClass());
807         }
808       }
809     };
810   }
811 
812   /** Alternative to AssertionError(String, Throwable), which doesn't exist in Java 1.6 */
813   private static AssertionError newAssertionError(String message, Throwable cause) {
814     AssertionError e = new AssertionError(message);
815     e.initCause(cause);
816     return e;
817   }
818 }
819