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