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