1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.stage; 27 28 import java.util.Arrays; 29 import java.util.Collection; 30 import java.util.HashSet; 31 import java.util.concurrent.CountDownLatch; 32 import java.util.concurrent.TimeUnit; 33 import java.util.concurrent.atomic.AtomicBoolean; 34 import javafx.application.Application; 35 import javafx.application.Platform; 36 import javafx.scene.Group; 37 import javafx.scene.Scene; 38 import javafx.scene.paint.Color; 39 import junit.framework.AssertionFailedError; 40 import org.junit.After; 41 import org.junit.AfterClass; 42 import org.junit.Before; 43 import org.junit.BeforeClass; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 import org.junit.runners.Parameterized; 47 import org.junit.runners.Parameterized.Parameters; 48 import util.Util; 49 50 import static org.junit.Assert.*; 51 import static util.Util.TIMEOUT; 52 53 /** 54 * Test program for showAndWait functionality. 55 */ 56 @RunWith(Parameterized.class) 57 public class ShowAndWaitTest { 58 59 // Maximum number of stages 60 private static final int MAX_STAGES = 10; 61 62 // Used to launch the application before running any test 63 private static final CountDownLatch launchLatch = new CountDownLatch(1); 64 65 // Singleton Application instance 66 private static MyApp myApp; 67 68 // Application class. An instance is created and initialized before running 69 // the first test, and it lives through the execution of all tests. 70 public static class MyApp extends Application { 71 private Stage primaryStage; 72 73 @Override public void init() { 74 ShowAndWaitTest.myApp = this; 75 } 76 77 @Override public void start(Stage primaryStage) throws Exception { 78 primaryStage.setTitle("Primary stage"); 79 Group root = new Group(); 80 Scene scene = new Scene(root); 81 scene.setFill(Color.LIGHTYELLOW); 82 primaryStage.setScene(scene); 83 primaryStage.setX(0); 84 primaryStage.setY(0); 85 primaryStage.setWidth(210); 86 primaryStage.setHeight(180); 87 88 this.primaryStage = primaryStage; 89 launchLatch.countDown(); 90 } 91 } 92 93 private static class TestStage extends Stage { 94 95 private TestStage(Modality modality) { 96 this(modality, modality == Modality.WINDOW_MODAL ? myApp.primaryStage : null); 97 } 98 99 private TestStage(Modality modality, Window owner) { 100 this.setTitle("Test stage"); 101 this.initModality(modality); 102 this.initOwner(owner); 103 104 Group root = new Group(); 105 Scene scene = new Scene(root); 106 this.setScene(scene); 107 this.setWidth(200); 108 this.setHeight(150); 109 this.setX(225); 110 this.setY(0); 111 } 112 } 113 114 @BeforeClass 115 public static void setupOnce() { 116 // Start the Application 117 new Thread(() -> Application.launch(MyApp.class, (String[])null)).start(); 118 119 try { 120 if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { 121 throw new AssertionFailedError("Timeout waiting for Application to launch"); 122 } 123 } catch (InterruptedException ex) { 124 AssertionFailedError err = new AssertionFailedError("Unexpected exception"); 125 err.initCause(ex); 126 throw err; 127 } 128 } 129 130 @AfterClass 131 public static void teardownOnce() { 132 Platform.exit(); 133 } 134 135 // Modality of the secondary stage(s) for a particular tests 136 final Modality modality; 137 138 // Set of stages being tested so that they can be hidden at the 139 // end of of a failing test 140 private HashSet<Stage> stages = new HashSet<Stage>(); 141 142 // Secondary stages used for testing 143 private Stage tmpStage1 = null; 144 private Stage tmpStage2 = null; 145 146 @Parameters 147 public static Collection getParams() { 148 return Arrays.asList(new Object[][] { 149 { Modality.NONE }, 150 { Modality.WINDOW_MODAL }, 151 { Modality.APPLICATION_MODAL }, 152 }); 153 } 154 155 public ShowAndWaitTest(Modality modality) { 156 this.modality = modality; 157 } 158 159 @Before 160 public void setupEach() { 161 assertNotNull(myApp); 162 assertNotNull(myApp.primaryStage); 163 } 164 165 @After 166 public void teardownEach() { 167 for (final Stage stage : stages) { 168 if (stage.isShowing()) { 169 System.err.println("Cleaning up stage after a failed test..."); 170 try { 171 Util.runAndWait(stage::hide); 172 } catch (Throwable t) { 173 System.err.println("WARNING: unable to hide stage after test failure"); 174 t.printStackTrace(System.err); 175 } 176 } 177 } 178 } 179 180 // ========================== TEST CASES ========================== 181 182 // This test must be run before any other test, because it first verifies 183 // that the primary stage is not yet showing, and finally shows it. 184 // Since JUnit does not guarantee test execution order, each test that 185 // relies on this must call ensureTest1(). This test does not use the 186 // test parameters and only runs one in total. 187 // 188 // Consider moving to the setupOnce method. 189 private static boolean test1Run = false; 190 public void ensureTest1() { 191 if (!test1Run) { 192 test1(); 193 } 194 } 195 196 @Test 197 public void test1() { 198 if (test1Run) { 199 return; 200 } 201 test1Run = true; 202 203 assertEquals(0, launchLatch.getCount()); 204 Util.runAndWait(() -> { 205 assertTrue(Platform.isFxApplicationThread()); 206 assertTrue(myApp.primaryStage.isPrimary()); 207 assertFalse(myApp.primaryStage.isShowing()); 208 209 // Verify that we cannot call showAndWait on the primaryStage 210 try { 211 myApp.primaryStage.showAndWait(); 212 throw new AssertionFailedError("Expected IllegalStateException was not thrown"); 213 } catch (IllegalStateException ex) { 214 } 215 216 myApp.primaryStage.show(); 217 }); 218 } 219 220 // Verify that we cannot construct a stage on a thread other than 221 // the FX Application thread 222 @Test (expected=IllegalStateException.class) 223 public void testConstructWrongThread() { 224 ensureTest1(); 225 assertFalse(Platform.isFxApplicationThread()); 226 227 // The following should throw IllegalStateException 228 tmpStage1 = new TestStage(modality); 229 stages.add(tmpStage1); 230 } 231 232 233 // Verify that we cannot call showAndWait on a thread other than 234 // the FX Application thread 235 @Test (expected=IllegalStateException.class) 236 public void testShowWaitWrongThread() { 237 ensureTest1(); 238 assertFalse(Platform.isFxApplicationThread()); 239 Util.runAndWait(() -> { 240 tmpStage1 = new TestStage(modality); 241 stages.add(tmpStage1); 242 assertFalse(tmpStage1.isPrimary()); 243 assertFalse(tmpStage1.isShowing()); 244 }); 245 assertNotNull(tmpStage1); 246 247 // The following should throw IllegalStateException 248 tmpStage1.showAndWait(); 249 } 250 251 // Verify that we cannot call showAndWait on a visible stage 252 @Test (expected=IllegalStateException.class) 253 public void testVisibleThrow() { 254 ensureTest1(); 255 Util.runAndWait(() -> { 256 tmpStage1 = new TestStage(modality); 257 stages.add(tmpStage1); 258 assertFalse(tmpStage1.isPrimary()); 259 assertFalse(tmpStage1.isShowing()); 260 tmpStage1.show(); 261 assertTrue(tmpStage1.isShowing()); 262 263 try { 264 // The following should throw IllegalStateException 265 tmpStage1.showAndWait(); 266 } finally { 267 tmpStage1.hide(); 268 } 269 }); 270 } 271 272 // Verify that show returns right away; hide the stage after 500 msec 273 @Test 274 public void testNotBlocking() { 275 ensureTest1(); 276 277 final AtomicBoolean stageShowReturned = new AtomicBoolean(false); 278 final AtomicBoolean hideActionReached = new AtomicBoolean(false); 279 280 Runnable rShow = () -> { 281 tmpStage1 = new TestStage(modality); 282 stages.add(tmpStage1); 283 assertFalse(tmpStage1.isPrimary()); 284 assertFalse(tmpStage1.isShowing()); 285 tmpStage1.show(); 286 stageShowReturned.set(true); 287 assertTrue(tmpStage1.isShowing()); 288 assertFalse(hideActionReached.get()); 289 }; 290 291 Runnable rHide = () -> { 292 assertNotNull(tmpStage1); 293 assertTrue(tmpStage1.isShowing()); 294 assertTrue(stageShowReturned.get()); 295 hideActionReached.set(true); 296 tmpStage1.hide(); 297 }; 298 299 Util.runAndWait(rShow, rHide); 300 301 assertFalse(tmpStage1.isShowing()); 302 } 303 304 // Verify that showAndWait blocks until the stage is hidden. 305 // Verify that the nested event loop exits immediately after 306 // the event handler that calls hide returns, before running 307 // the next Runnable. 308 @Test 309 public void testSingle() { 310 ensureTest1(); 311 312 final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false); 313 final AtomicBoolean hide1EventReached = new AtomicBoolean(false); 314 final AtomicBoolean nextRunnableReached = new AtomicBoolean(false); 315 316 Runnable rShow1 = () -> { 317 tmpStage1 = new TestStage(modality); 318 stages.add(tmpStage1); 319 assertFalse(tmpStage1.isPrimary()); 320 assertFalse(tmpStage1.isShowing()); 321 tmpStage1.showAndWait(); 322 stage1ShowReturned.set(true); 323 assertFalse(tmpStage1.isShowing()); 324 assertTrue(hide1EventReached.get()); 325 assertFalse(nextRunnableReached.get()); 326 }; 327 328 Runnable rHide1 = () -> { 329 hide1EventReached.set(true); 330 assertFalse(stage1ShowReturned.get()); 331 assertNotNull(tmpStage1); 332 tmpStage1.hide(); 333 Util.sleep(1); 334 assertFalse(stage1ShowReturned.get()); 335 }; 336 337 Runnable rNext = () -> { 338 // This should happen after the nested event loop exits 339 nextRunnableReached.set(true); 340 }; 341 342 Util.runAndWait(rShow1, rHide1, rNext); 343 344 assertFalse(tmpStage1.isShowing()); 345 } 346 347 // Verify that showAndWait blocks until the stage is hidden. 348 // Verify that the nested event loop exits immediately after 349 // the event handler that calls hide returns, before running 350 // the next Runnable (called from rShow1 after showAndWait returns). 351 352 @Test 353 public void testSingle_Chained() { 354 ensureTest1(); 355 356 final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false); 357 final AtomicBoolean hide1EventReached = new AtomicBoolean(false); 358 final AtomicBoolean nextRunnableReached = new AtomicBoolean(false); 359 360 Runnable rShow1 = () -> { 361 tmpStage1 = new TestStage(modality); 362 stages.add(tmpStage1); 363 assertFalse(tmpStage1.isPrimary()); 364 assertFalse(tmpStage1.isShowing()); 365 tmpStage1.showAndWait(); 366 stage1ShowReturned.set(true); 367 assertFalse(tmpStage1.isShowing()); 368 assertTrue(hide1EventReached.get()); 369 assertFalse(nextRunnableReached.get()); 370 }; 371 372 Runnable rHide1 = () -> { 373 hide1EventReached.set(true); 374 assertFalse(stage1ShowReturned.get()); 375 assertNotNull(tmpStage1); 376 tmpStage1.hide(); 377 Util.sleep(1); 378 assertFalse(stage1ShowReturned.get()); 379 Platform.runLater(() -> { 380 // This should happen after the nested event loop exits 381 nextRunnableReached.set(true); 382 }); 383 }; 384 385 Util.runAndWait(rShow1, rHide1); 386 387 assertFalse(tmpStage1.isShowing()); 388 } 389 390 // Verify two nested event loops, with the stages being hidden in the 391 // reverse order that they are shown 392 @Test 393 public void testTwoNested() { 394 ensureTest1(); 395 396 final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false); 397 final AtomicBoolean hide1EventReached = new AtomicBoolean(false); 398 final AtomicBoolean stage2ShowReturned = new AtomicBoolean(false); 399 final AtomicBoolean hide2EventReached = new AtomicBoolean(false); 400 401 Runnable rShow1 = () -> { 402 tmpStage1 = new TestStage(modality); 403 stages.add(tmpStage1); 404 assertFalse(tmpStage1.isPrimary()); 405 assertFalse(tmpStage1.isShowing()); 406 tmpStage1.showAndWait(); 407 stage1ShowReturned.set(true); 408 assertFalse(tmpStage1.isShowing()); 409 assertTrue(stage2ShowReturned.get()); 410 assertTrue(hide1EventReached.get()); 411 assertTrue(hide2EventReached.get()); 412 }; 413 414 Runnable rShow2 = () -> { 415 tmpStage2 = new TestStage(modality); 416 stages.add(tmpStage2); 417 assertFalse(tmpStage2.isPrimary()); 418 assertFalse(tmpStage2.isShowing()); 419 tmpStage2.showAndWait(); 420 stage2ShowReturned.set(true); 421 assertFalse(stage1ShowReturned.get()); 422 assertFalse(tmpStage2.isShowing()); 423 assertTrue(hide2EventReached.get()); 424 assertFalse(hide1EventReached.get()); 425 }; 426 427 Runnable rHide1 = () -> { 428 hide1EventReached.set(true); 429 assertFalse(stage1ShowReturned.get()); 430 assertTrue(stage2ShowReturned.get()); 431 assertTrue(hide2EventReached.get()); 432 assertNotNull(tmpStage1); 433 tmpStage1.hide(); 434 Util.sleep(1); 435 assertFalse(stage1ShowReturned.get()); 436 }; 437 438 Runnable rHide2 = () -> { 439 hide2EventReached.set(true); 440 assertFalse(stage2ShowReturned.get()); 441 assertFalse(stage1ShowReturned.get()); 442 assertFalse(hide1EventReached.get()); 443 assertNotNull(tmpStage2); 444 tmpStage2.hide(); 445 Util.sleep(1); 446 assertFalse(stage2ShowReturned.get()); 447 }; 448 449 Util.runAndWait(rShow1, rShow2, rHide2, rHide1); 450 451 assertFalse(tmpStage1.isShowing()); 452 assertFalse(tmpStage2.isShowing()); 453 } 454 455 // Verify two nested event loops, with the stages being hidden in the 456 // same order that they are shown 457 @Test 458 public void testTwoInterleaved() { 459 ensureTest1(); 460 461 final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false); 462 final AtomicBoolean hide1EventReached = new AtomicBoolean(false); 463 final AtomicBoolean stage2ShowReturned = new AtomicBoolean(false); 464 final AtomicBoolean hide2EventReached = new AtomicBoolean(false); 465 466 Runnable rShow1 = () -> { 467 tmpStage1 = new TestStage(modality); 468 stages.add(tmpStage1); 469 assertFalse(tmpStage1.isPrimary()); 470 assertFalse(tmpStage1.isShowing()); 471 tmpStage1.showAndWait(); 472 stage1ShowReturned.set(true); 473 assertFalse(tmpStage1.isShowing()); 474 assertTrue(stage2ShowReturned.get()); 475 assertTrue(hide1EventReached.get()); 476 assertTrue(hide2EventReached.get()); 477 }; 478 479 Runnable rShow2 = () -> { 480 tmpStage2 = new TestStage(modality); 481 stages.add(tmpStage2); 482 assertFalse(tmpStage2.isPrimary()); 483 assertFalse(tmpStage2.isShowing()); 484 tmpStage2.showAndWait(); 485 stage2ShowReturned.set(true); 486 assertFalse(tmpStage2.isShowing()); 487 assertFalse(stage1ShowReturned.get()); 488 assertTrue(hide2EventReached.get()); 489 assertTrue(hide1EventReached.get()); 490 }; 491 492 Runnable rHide1 = () -> { 493 hide1EventReached.set(true); 494 assertFalse(stage1ShowReturned.get()); 495 assertFalse(stage2ShowReturned.get()); 496 assertFalse(hide2EventReached.get()); 497 assertNotNull(tmpStage1); 498 tmpStage1.hide(); 499 Util.sleep(1); 500 assertFalse(stage1ShowReturned.get()); 501 }; 502 503 Runnable rHide2 = () -> { 504 hide2EventReached.set(true); 505 assertFalse(stage2ShowReturned.get()); 506 assertFalse(stage1ShowReturned.get()); 507 assertTrue(hide1EventReached.get()); 508 assertNotNull(tmpStage2); 509 tmpStage2.hide(); 510 Util.sleep(1); 511 assertFalse(stage2ShowReturned.get()); 512 }; 513 514 Util.runAndWait(rShow1, rShow2, rHide1, rHide2); 515 516 assertFalse(tmpStage1.isShowing()); 517 assertFalse(tmpStage2.isShowing()); 518 } 519 520 // Verify multiple nested event loops, with the stages being hidden in the 521 // reverse order that they are shown 522 @Test 523 public void testMultipleNested() { 524 ensureTest1(); 525 526 final int N = MAX_STAGES; 527 final Stage[] tmpStage = new Stage[N]; 528 final AtomicBoolean[] stageShowReturned = new AtomicBoolean[N]; 529 final AtomicBoolean[] hideEventReached = new AtomicBoolean[N]; 530 final Runnable[] rShow = new Runnable[N]; 531 final Runnable[] rHide = new Runnable[N]; 532 533 for (int i = 0; i < N; i++) { 534 final int idx = i; 535 stageShowReturned[idx] = new AtomicBoolean(false); 536 hideEventReached[idx] = new AtomicBoolean(false); 537 rShow[idx] = () -> { 538 tmpStage[idx] = new TestStage(modality); 539 stages.add(tmpStage[idx]); 540 assertFalse(tmpStage[idx].isShowing()); 541 tmpStage[idx].showAndWait(); 542 stageShowReturned[idx].set(true); 543 assertFalse(tmpStage[idx].isShowing()); 544 assertTrue(hideEventReached[idx].get()); 545 for (int j = 0; j < idx; j++) { 546 assertFalse(stageShowReturned[j].get()); 547 assertFalse(hideEventReached[j].get()); 548 } 549 for (int j = idx+1; j < N; j++) { 550 assertTrue(stageShowReturned[j].get()); 551 assertTrue(hideEventReached[j].get()); 552 } 553 }; 554 555 rHide[idx] = () -> { 556 hideEventReached[idx].set(true); 557 assertFalse(stageShowReturned[idx].get()); 558 for (int j = 0; j < idx; j++) { 559 assertFalse(stageShowReturned[j].get()); 560 assertFalse(hideEventReached[j].get()); 561 } 562 for (int j = idx+1; j < N; j++) { 563 assertTrue(stageShowReturned[j].get()); 564 assertTrue(hideEventReached[j].get()); 565 } 566 assertNotNull(tmpStage[idx]); 567 tmpStage[idx].hide(); 568 Util.sleep(1); 569 assertFalse(stageShowReturned[idx].get()); 570 }; 571 } 572 573 final Runnable[] runnables = new Runnable[2*N]; 574 for (int i = 0; i < N; i++) { 575 runnables[i] = rShow[i]; 576 runnables[(2*N - i - 1)] = rHide[i]; 577 } 578 Util.runAndWait(runnables); 579 580 for (int i = 0; i < N; i++) { 581 assertFalse(tmpStage[i].isShowing()); 582 } 583 } 584 585 // Verify multiple nested event loops, with the stages being hidden in the 586 // reverse order that they are shown 587 @Test 588 public void testMultipleInterleaved() { 589 ensureTest1(); 590 591 final int N = MAX_STAGES; 592 final Stage[] tmpStage = new Stage[N]; 593 final AtomicBoolean[] stageShowReturned = new AtomicBoolean[N]; 594 final AtomicBoolean[] hideEventReached = new AtomicBoolean[N]; 595 final Runnable[] rShow = new Runnable[N]; 596 final Runnable[] rHide = new Runnable[N]; 597 598 for (int i = 0; i < N; i++) { 599 final int idx = i; 600 stageShowReturned[idx] = new AtomicBoolean(false); 601 hideEventReached[idx] = new AtomicBoolean(false); 602 rShow[idx] = () -> { 603 tmpStage[idx] = new TestStage(modality); 604 stages.add(tmpStage[idx]); 605 assertFalse(tmpStage[idx].isShowing()); 606 tmpStage[idx].showAndWait(); 607 stageShowReturned[idx].set(true); 608 assertFalse(tmpStage[idx].isShowing()); 609 assertTrue(hideEventReached[idx].get()); 610 for (int j = 0; j < idx; j++) { 611 assertFalse(stageShowReturned[j].get()); 612 assertTrue(hideEventReached[j].get()); 613 } 614 for (int j = idx+1; j < N; j++) { 615 assertTrue(stageShowReturned[j].get()); 616 assertTrue(hideEventReached[j].get()); 617 } 618 }; 619 620 rHide[idx] = () -> { 621 hideEventReached[idx].set(true); 622 assertFalse(stageShowReturned[idx].get()); 623 for (int j = 0; j < idx; j++) { 624 assertFalse(stageShowReturned[j].get()); 625 assertTrue(hideEventReached[j].get()); 626 } 627 for (int j = idx+1; j < N; j++) { 628 assertFalse(stageShowReturned[j].get()); 629 assertFalse(hideEventReached[j].get()); 630 } 631 assertNotNull(tmpStage[idx]); 632 tmpStage[idx].hide(); 633 Util.sleep(1); 634 assertFalse(stageShowReturned[idx].get()); 635 }; 636 } 637 638 final Runnable[] runnables = new Runnable[2*N]; 639 for (int i = 0; i < N; i++) { 640 runnables[i] = rShow[i]; 641 runnables[N+i] = rHide[i]; 642 } 643 Util.runAndWait(runnables); 644 645 for (int i = 0; i < N; i++) { 646 assertFalse(tmpStage[i].isShowing()); 647 } 648 } 649 650 }