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