1 /*
   2  * Copyright (c) 2012, 2015, 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 test.javafx.stage;
  27 
  28 import javafx.stage.StageShim;
  29 import java.util.Arrays;
  30 import java.util.Collection;
  31 import java.util.HashSet;
  32 import java.util.concurrent.CountDownLatch;
  33 import java.util.concurrent.TimeUnit;
  34 import java.util.concurrent.atomic.AtomicBoolean;
  35 import java.util.concurrent.atomic.AtomicReference;
  36 import javafx.animation.KeyFrame;
  37 import javafx.animation.Timeline;
  38 import javafx.application.Application;
  39 import javafx.application.Platform;
  40 import javafx.print.PrinterJob;
  41 import javafx.scene.Group;
  42 import javafx.scene.Scene;
  43 import javafx.scene.control.Alert;
  44 import javafx.scene.paint.Color;
  45 import javafx.scene.shape.Rectangle;
  46 import javafx.stage.Modality;
  47 import javafx.stage.Stage;
  48 import javafx.stage.StageShim;
  49 import javafx.stage.Window;
  50 import javafx.util.Duration;
  51 import junit.framework.AssertionFailedError;
  52 import org.junit.After;
  53 import org.junit.AfterClass;
  54 import org.junit.Before;
  55 import org.junit.BeforeClass;
  56 import org.junit.Test;
  57 import org.junit.runner.RunWith;
  58 import org.junit.runners.Parameterized;
  59 import org.junit.runners.Parameterized.Parameters;
  60 import test.util.Util;
  61 
  62 import static org.junit.Assert.*;
  63 import static org.junit.Assume.*;
  64 import static test.util.Util.TIMEOUT;
  65 
  66 /**
  67  * Test program for showAndWait functionality.
  68  */
  69 @RunWith(Parameterized.class)
  70 public class ShowAndWaitTest {
  71 
  72     // Maximum number of stages
  73     private static final int MAX_STAGES = 10;
  74 
  75     // Used to launch the application before running any test
  76     private static final CountDownLatch launchLatch = new CountDownLatch(1);
  77 
  78     // Singleton Application instance
  79     private static MyApp myApp;
  80 
  81     // Application class. An instance is created and initialized before running
  82     // the first test, and it lives through the execution of all tests.
  83     public static class MyApp extends Application {
  84         private Stage primaryStage;
  85 
  86         @Override public void init() {
  87             ShowAndWaitTest.myApp = this;
  88         }
  89 
  90         @Override public void start(Stage primaryStage) throws Exception {
  91             primaryStage.setTitle("Primary stage");
  92             Group root = new Group();
  93             Scene scene = new Scene(root);
  94             scene.setFill(Color.LIGHTYELLOW);
  95             primaryStage.setScene(scene);
  96             primaryStage.setX(0);
  97             primaryStage.setY(0);
  98             primaryStage.setWidth(210);
  99             primaryStage.setHeight(180);
 100 
 101             this.primaryStage = primaryStage;
 102             launchLatch.countDown();
 103         }
 104     }
 105 
 106     private static class TestStage extends Stage {
 107 
 108         private TestStage(Modality modality) {
 109             this(modality, modality == Modality.WINDOW_MODAL ? myApp.primaryStage : null);
 110         }
 111 
 112         private TestStage(Modality modality, Window owner) {
 113             this.setTitle("Test stage");
 114             this.initModality(modality);
 115             this.initOwner(owner);
 116 
 117             Group root = new Group();
 118             Scene scene = new Scene(root);
 119             this.setScene(scene);
 120             this.setWidth(200);
 121             this.setHeight(150);
 122             this.setX(225);
 123             this.setY(0);
 124         }
 125     }
 126 
 127     @BeforeClass
 128     public static void setupOnce() {
 129         // Start the Application
 130         new Thread(() -> Application.launch(MyApp.class, (String[])null)).start();
 131 
 132         try {
 133             if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
 134                 throw new AssertionFailedError("Timeout waiting for Application to launch");
 135             }
 136         } catch (InterruptedException ex) {
 137             AssertionFailedError err = new AssertionFailedError("Unexpected exception");
 138             err.initCause(ex);
 139             throw err;
 140         }
 141     }
 142 
 143     @AfterClass
 144     public static void teardownOnce() {
 145         Platform.exit();
 146     }
 147 
 148     // Modality of the secondary stage(s) for a particular tests
 149     final Modality modality;
 150 
 151     // Set of stages being tested so that they can be hidden at the
 152     // end of of a failing test
 153     private HashSet<Stage> stages = new HashSet<Stage>();
 154 
 155     // Secondary stages used for testing
 156     private Stage tmpStage1 = null;
 157     private Stage tmpStage2 = null;
 158 
 159     @Parameters
 160     public static Collection getParams() {
 161         return Arrays.asList(new Object[][] {
 162             { Modality.NONE },
 163             { Modality.WINDOW_MODAL },
 164             { Modality.APPLICATION_MODAL },
 165         });
 166     }
 167 
 168     public ShowAndWaitTest(Modality modality) {
 169         this.modality = modality;
 170     }
 171 
 172     @Before
 173     public void setupEach() {
 174         assertNotNull(myApp);
 175         assertNotNull(myApp.primaryStage);
 176     }
 177 
 178     @After
 179     public void teardownEach() {
 180         for (final Stage stage : stages) {
 181             if (stage.isShowing()) {
 182                 System.err.println("Cleaning up stage after a failed test...");
 183                 try {
 184                     Util.runAndWait(stage::hide);
 185                 } catch (Throwable t) {
 186                     System.err.println("WARNING: unable to hide stage after test failure");
 187                     t.printStackTrace(System.err);
 188                 }
 189             }
 190         }
 191     }
 192 
 193     // ========================== TEST CASES ==========================
 194 
 195     // This test must be run before any other test, because it first verifies
 196     // that the primary stage is not yet showing, and finally shows it.
 197     // Since JUnit does not guarantee test execution order, each test that
 198     // relies on this must call ensureTest1(). This test does not use the
 199     // test parameters and only runs one in total.
 200     //
 201     // Consider moving to the setupOnce method.
 202     private static boolean test1Run = false;
 203     public void ensureTest1() {
 204         if (!test1Run) {
 205             test1();
 206         }
 207     }
 208 
 209     @Test
 210     public void test1() {
 211         if (test1Run) {
 212             return;
 213         }
 214         test1Run = true;
 215 
 216         assertEquals(0, launchLatch.getCount());
 217         Util.runAndWait(() -> {
 218             assertTrue(Platform.isFxApplicationThread());
 219             assertTrue(StageShim.isPrimary(myApp.primaryStage));
 220             assertFalse(myApp.primaryStage.isShowing());
 221 
 222             // Verify that we cannot call showAndWait on the primaryStage
 223             try {
 224                 myApp.primaryStage.showAndWait();
 225                 throw new AssertionFailedError("Expected IllegalStateException was not thrown");
 226             } catch (IllegalStateException ex) {
 227             }
 228 
 229             myApp.primaryStage.show();
 230         });
 231     }
 232 
 233     // Verify that we cannot construct a stage on a thread other than
 234     // the FX Application thread
 235     @Test (expected=IllegalStateException.class)
 236     public void testConstructWrongThread() {
 237         ensureTest1();
 238         assertFalse(Platform.isFxApplicationThread());
 239 
 240         // The following should throw IllegalStateException
 241         tmpStage1 = new TestStage(modality);
 242         stages.add(tmpStage1);
 243     }
 244 
 245 
 246     // Verify that we cannot call showAndWait on a thread other than
 247     // the FX Application thread
 248     @Test (expected=IllegalStateException.class)
 249     public void testShowWaitWrongThread() {
 250         ensureTest1();
 251         assertFalse(Platform.isFxApplicationThread());
 252         Util.runAndWait(() -> {
 253             tmpStage1 = new TestStage(modality);
 254             stages.add(tmpStage1);
 255             assertFalse(StageShim.isPrimary(tmpStage1));
 256             assertFalse(tmpStage1.isShowing());
 257         });
 258         assertNotNull(tmpStage1);
 259 
 260         // The following should throw IllegalStateException
 261         tmpStage1.showAndWait();
 262     }
 263 
 264     // Verify that we cannot call showAndWait on a visible stage
 265     @Test (expected=IllegalStateException.class)
 266     public void testVisibleThrow() {
 267         ensureTest1();
 268         Util.runAndWait(() -> {
 269             tmpStage1 = new TestStage(modality);
 270             stages.add(tmpStage1);
 271             assertFalse(StageShim.isPrimary(tmpStage1));
 272             assertFalse(tmpStage1.isShowing());
 273             tmpStage1.show();
 274             assertTrue(tmpStage1.isShowing());
 275 
 276             try {
 277                 // The following should throw IllegalStateException
 278                 tmpStage1.showAndWait();
 279             } finally {
 280                 tmpStage1.hide();
 281             }
 282         });
 283     }
 284 
 285     // Verify that show returns right away; hide the stage after 500 msec
 286     @Test
 287     public void testNotBlocking() {
 288         ensureTest1();
 289 
 290         final AtomicBoolean stageShowReturned = new AtomicBoolean(false);
 291         final AtomicBoolean hideActionReached = new AtomicBoolean(false);
 292 
 293         Runnable rShow = () -> {
 294             tmpStage1 = new TestStage(modality);
 295             stages.add(tmpStage1);
 296             assertFalse(StageShim.isPrimary(tmpStage1));
 297             assertFalse(tmpStage1.isShowing());
 298             tmpStage1.show();
 299             stageShowReturned.set(true);
 300             assertTrue(tmpStage1.isShowing());
 301             assertFalse(hideActionReached.get());
 302         };
 303 
 304         Runnable rHide = () -> {
 305             assertNotNull(tmpStage1);
 306             assertTrue(tmpStage1.isShowing());
 307             assertTrue(stageShowReturned.get());
 308             hideActionReached.set(true);
 309             tmpStage1.hide();
 310         };
 311 
 312         Util.runAndWait(rShow, rHide);
 313 
 314         assertFalse(tmpStage1.isShowing());
 315     }
 316 
 317     // Verify that showAndWait blocks until the stage is hidden.
 318     // Verify that the nested event loop exits immediately after
 319     // the event handler that calls hide returns, before running
 320     // the next Runnable.
 321     @Test
 322     public void testSingle() {
 323         ensureTest1();
 324 
 325         final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false);
 326         final AtomicBoolean hide1EventReached = new AtomicBoolean(false);
 327         final AtomicBoolean nextRunnableReached = new AtomicBoolean(false);
 328 
 329         Runnable rShow1 = () -> {
 330             tmpStage1 = new TestStage(modality);
 331             stages.add(tmpStage1);
 332             assertFalse(StageShim.isPrimary(tmpStage1));
 333             assertFalse(tmpStage1.isShowing());
 334             tmpStage1.showAndWait();
 335             stage1ShowReturned.set(true);
 336             assertFalse(tmpStage1.isShowing());
 337             assertTrue(hide1EventReached.get());
 338             assertFalse(nextRunnableReached.get());
 339         };
 340 
 341         Runnable rHide1 = () -> {
 342             hide1EventReached.set(true);
 343             assertFalse(stage1ShowReturned.get());
 344             assertNotNull(tmpStage1);
 345             tmpStage1.hide();
 346             Util.sleep(1);
 347             assertFalse(stage1ShowReturned.get());
 348         };
 349 
 350         Runnable rNext = () -> {
 351             // This should happen after the nested event loop exits
 352             nextRunnableReached.set(true);
 353         };
 354 
 355         Util.runAndWait(rShow1, rHide1, rNext);
 356 
 357         assertFalse(tmpStage1.isShowing());
 358     }
 359 
 360     // Verify that showAndWait blocks until the stage is hidden.
 361     // Verify that the nested event loop exits immediately after
 362     // the event handler that calls hide returns, before running
 363     // the next Runnable (called from rShow1 after showAndWait returns).
 364 
 365     @Test
 366     public void testSingle_Chained() {
 367         ensureTest1();
 368 
 369         final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false);
 370         final AtomicBoolean hide1EventReached = new AtomicBoolean(false);
 371         final AtomicBoolean nextRunnableReached = new AtomicBoolean(false);
 372 
 373         Runnable rShow1 = () -> {
 374             tmpStage1 = new TestStage(modality);
 375             stages.add(tmpStage1);
 376             assertFalse(StageShim.isPrimary(tmpStage1));
 377             assertFalse(tmpStage1.isShowing());
 378             tmpStage1.showAndWait();
 379             stage1ShowReturned.set(true);
 380             assertFalse(tmpStage1.isShowing());
 381             assertTrue(hide1EventReached.get());
 382             assertFalse(nextRunnableReached.get());
 383         };
 384 
 385         Runnable rHide1 = () -> {
 386             hide1EventReached.set(true);
 387             assertFalse(stage1ShowReturned.get());
 388             assertNotNull(tmpStage1);
 389             tmpStage1.hide();
 390             Util.sleep(1);
 391             assertFalse(stage1ShowReturned.get());
 392             Platform.runLater(() -> {
 393                 // This should happen after the nested event loop exits
 394                 nextRunnableReached.set(true);
 395             });
 396         };
 397 
 398         Util.runAndWait(rShow1, rHide1);
 399 
 400         assertFalse(tmpStage1.isShowing());
 401     }
 402 
 403     // Verify two nested event loops, with the stages being hidden in the
 404     // reverse order that they are shown
 405     @Test
 406     public void testTwoNested() {
 407         ensureTest1();
 408 
 409         final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false);
 410         final AtomicBoolean hide1EventReached = new AtomicBoolean(false);
 411         final AtomicBoolean stage2ShowReturned = new AtomicBoolean(false);
 412         final AtomicBoolean hide2EventReached = new AtomicBoolean(false);
 413 
 414         Runnable rShow1 = () -> {
 415             tmpStage1 = new TestStage(modality);
 416             stages.add(tmpStage1);
 417             assertFalse(StageShim.isPrimary(tmpStage1));
 418             assertFalse(tmpStage1.isShowing());
 419             tmpStage1.showAndWait();
 420             stage1ShowReturned.set(true);
 421             assertFalse(tmpStage1.isShowing());
 422             assertTrue(stage2ShowReturned.get());
 423             assertTrue(hide1EventReached.get());
 424             assertTrue(hide2EventReached.get());
 425         };
 426 
 427         Runnable rShow2 = () -> {
 428             tmpStage2 = new TestStage(modality);
 429             stages.add(tmpStage2);
 430             assertFalse(StageShim.isPrimary(tmpStage2));
 431             assertFalse(tmpStage2.isShowing());
 432             tmpStage2.showAndWait();
 433             stage2ShowReturned.set(true);
 434             assertFalse(stage1ShowReturned.get());
 435             assertFalse(tmpStage2.isShowing());
 436             assertTrue(hide2EventReached.get());
 437             assertFalse(hide1EventReached.get());
 438         };
 439 
 440         Runnable rHide1 = () -> {
 441             hide1EventReached.set(true);
 442             assertFalse(stage1ShowReturned.get());
 443             assertTrue(stage2ShowReturned.get());
 444             assertTrue(hide2EventReached.get());
 445             assertNotNull(tmpStage1);
 446             tmpStage1.hide();
 447             Util.sleep(1);
 448             assertFalse(stage1ShowReturned.get());
 449         };
 450 
 451         Runnable rHide2 = () -> {
 452             hide2EventReached.set(true);
 453             assertFalse(stage2ShowReturned.get());
 454             assertFalse(stage1ShowReturned.get());
 455             assertFalse(hide1EventReached.get());
 456             assertNotNull(tmpStage2);
 457             tmpStage2.hide();
 458             Util.sleep(1);
 459             assertFalse(stage2ShowReturned.get());
 460         };
 461 
 462         Util.runAndWait(rShow1, rShow2, rHide2, rHide1);
 463 
 464         assertFalse(tmpStage1.isShowing());
 465         assertFalse(tmpStage2.isShowing());
 466     }
 467 
 468     // Verify two nested event loops, with the stages being hidden in the
 469     // same order that they are shown
 470     @Test
 471     public void testTwoInterleaved() {
 472         ensureTest1();
 473 
 474         final AtomicBoolean stage1ShowReturned = new AtomicBoolean(false);
 475         final AtomicBoolean hide1EventReached = new AtomicBoolean(false);
 476         final AtomicBoolean stage2ShowReturned = new AtomicBoolean(false);
 477         final AtomicBoolean hide2EventReached = new AtomicBoolean(false);
 478 
 479         Runnable rShow1 = () -> {
 480             tmpStage1 = new TestStage(modality);
 481             stages.add(tmpStage1);
 482             assertFalse(StageShim.isPrimary(tmpStage1));
 483             assertFalse(tmpStage1.isShowing());
 484             tmpStage1.showAndWait();
 485             stage1ShowReturned.set(true);
 486             assertFalse(tmpStage1.isShowing());
 487             assertTrue(stage2ShowReturned.get());
 488             assertTrue(hide1EventReached.get());
 489             assertTrue(hide2EventReached.get());
 490         };
 491 
 492         Runnable rShow2 = () -> {
 493             tmpStage2 = new TestStage(modality);
 494             stages.add(tmpStage2);
 495             assertFalse(StageShim.isPrimary(tmpStage2));
 496             assertFalse(tmpStage2.isShowing());
 497             tmpStage2.showAndWait();
 498             stage2ShowReturned.set(true);
 499             assertFalse(tmpStage2.isShowing());
 500             assertFalse(stage1ShowReturned.get());
 501             assertTrue(hide2EventReached.get());
 502             assertTrue(hide1EventReached.get());
 503         };
 504 
 505         Runnable rHide1 = () -> {
 506             hide1EventReached.set(true);
 507             assertFalse(stage1ShowReturned.get());
 508             assertFalse(stage2ShowReturned.get());
 509             assertFalse(hide2EventReached.get());
 510             assertNotNull(tmpStage1);
 511             tmpStage1.hide();
 512             Util.sleep(1);
 513             assertFalse(stage1ShowReturned.get());
 514         };
 515 
 516         Runnable rHide2 = () -> {
 517             hide2EventReached.set(true);
 518             assertFalse(stage2ShowReturned.get());
 519             assertFalse(stage1ShowReturned.get());
 520             assertTrue(hide1EventReached.get());
 521             assertNotNull(tmpStage2);
 522             tmpStage2.hide();
 523             Util.sleep(1);
 524             assertFalse(stage2ShowReturned.get());
 525         };
 526 
 527         Util.runAndWait(rShow1, rShow2, rHide1, rHide2);
 528 
 529         assertFalse(tmpStage1.isShowing());
 530         assertFalse(tmpStage2.isShowing());
 531     }
 532 
 533     // Verify multiple nested event loops, with the stages being hidden in the
 534     // reverse order that they are shown
 535     @Test
 536     public void testMultipleNested() {
 537         ensureTest1();
 538 
 539         final int N = MAX_STAGES;
 540         final Stage[] tmpStage = new Stage[N];
 541         final AtomicBoolean[] stageShowReturned = new AtomicBoolean[N];
 542         final AtomicBoolean[] hideEventReached = new AtomicBoolean[N];
 543         final Runnable[] rShow = new Runnable[N];
 544         final Runnable[] rHide = new Runnable[N];
 545 
 546         for (int i = 0; i < N; i++) {
 547             final int idx = i;
 548             stageShowReturned[idx] = new AtomicBoolean(false);
 549             hideEventReached[idx] = new AtomicBoolean(false);
 550             rShow[idx] = () -> {
 551                 tmpStage[idx] = new TestStage(modality);
 552                 stages.add(tmpStage[idx]);
 553                 assertFalse(tmpStage[idx].isShowing());
 554                 tmpStage[idx].showAndWait();
 555                 stageShowReturned[idx].set(true);
 556                 assertFalse(tmpStage[idx].isShowing());
 557                 assertTrue(hideEventReached[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             };
 567 
 568             rHide[idx] = () -> {
 569                 hideEventReached[idx].set(true);
 570                 assertFalse(stageShowReturned[idx].get());
 571                 for (int j = 0; j < idx; j++) {
 572                     assertFalse(stageShowReturned[j].get());
 573                     assertFalse(hideEventReached[j].get());
 574                 }
 575                 for (int j = idx+1; j < N; j++) {
 576                     assertTrue(stageShowReturned[j].get());
 577                     assertTrue(hideEventReached[j].get());
 578                 }
 579                 assertNotNull(tmpStage[idx]);
 580                 tmpStage[idx].hide();
 581                 Util.sleep(1);
 582                 assertFalse(stageShowReturned[idx].get());
 583             };
 584         }
 585 
 586         final Runnable[] runnables = new Runnable[2*N];
 587         for (int i = 0; i < N; i++) {
 588             runnables[i] = rShow[i];
 589             runnables[(2*N - i - 1)] = rHide[i];
 590         }
 591         Util.runAndWait(runnables);
 592 
 593         for (int i = 0; i < N; i++) {
 594             assertFalse(tmpStage[i].isShowing());
 595         }
 596     }
 597 
 598     // Verify multiple nested event loops, with the stages being hidden in the
 599     // reverse order that they are shown
 600     @Test
 601     public void testMultipleInterleaved() {
 602         ensureTest1();
 603 
 604         final int N = MAX_STAGES;
 605         final Stage[] tmpStage = new Stage[N];
 606         final AtomicBoolean[] stageShowReturned = new AtomicBoolean[N];
 607         final AtomicBoolean[] hideEventReached = new AtomicBoolean[N];
 608         final Runnable[] rShow = new Runnable[N];
 609         final Runnable[] rHide = new Runnable[N];
 610 
 611         for (int i = 0; i < N; i++) {
 612             final int idx = i;
 613             stageShowReturned[idx] = new AtomicBoolean(false);
 614             hideEventReached[idx] = new AtomicBoolean(false);
 615             rShow[idx] = () -> {
 616                 tmpStage[idx] = new TestStage(modality);
 617                 stages.add(tmpStage[idx]);
 618                 assertFalse(tmpStage[idx].isShowing());
 619                 tmpStage[idx].showAndWait();
 620                 stageShowReturned[idx].set(true);
 621                 assertFalse(tmpStage[idx].isShowing());
 622                 assertTrue(hideEventReached[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                     assertTrue(stageShowReturned[j].get());
 629                     assertTrue(hideEventReached[j].get());
 630                 }
 631             };
 632 
 633             rHide[idx] = () -> {
 634                 hideEventReached[idx].set(true);
 635                 assertFalse(stageShowReturned[idx].get());
 636                 for (int j = 0; j < idx; j++) {
 637                     assertFalse(stageShowReturned[j].get());
 638                     assertTrue(hideEventReached[j].get());
 639                 }
 640                 for (int j = idx+1; j < N; j++) {
 641                     assertFalse(stageShowReturned[j].get());
 642                     assertFalse(hideEventReached[j].get());
 643                 }
 644                 assertNotNull(tmpStage[idx]);
 645                 tmpStage[idx].hide();
 646                 Util.sleep(1);
 647                 assertFalse(stageShowReturned[idx].get());
 648             };
 649         }
 650 
 651         final Runnable[] runnables = new Runnable[2*N];
 652         for (int i = 0; i < N; i++) {
 653             runnables[i] = rShow[i];
 654             runnables[N+i] = rHide[i];
 655         }
 656         Util.runAndWait(runnables);
 657 
 658         for (int i = 0; i < N; i++) {
 659             assertFalse(tmpStage[i].isShowing());
 660         }
 661     }
 662 
 663     // Verify that Stage.showAndWait throws an exception if called from an
 664     // animation timeline.
 665     @Test
 666     public void testTimeline() throws Throwable {
 667         ensureTest1();
 668 
 669         final CountDownLatch animationDone = new CountDownLatch(1);
 670         final AtomicReference<Throwable> error = new AtomicReference<>(null);
 671 
 672         KeyFrame kf = new KeyFrame(Duration.millis(200), e -> {
 673             try {
 674                 tmpStage1 = new TestStage(modality);
 675                 stages.add(tmpStage1);
 676                 assertFalse(StageShim.isPrimary(tmpStage1));
 677                 assertFalse(tmpStage1.isShowing());
 678                 try {
 679                     tmpStage1.showAndWait();
 680                     fail("Did not get expected exception from showAndWait");
 681                 } catch (IllegalStateException ex) {
 682                     // Good
 683                 }
 684                 assertFalse(tmpStage1.isShowing());
 685             } catch (Throwable t) {
 686                 error.set(t);
 687             }
 688             animationDone.countDown();
 689         });
 690         Timeline timeline = new Timeline(kf);
 691         timeline.play();
 692 
 693         try {
 694             if (!animationDone.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
 695                 fail("Timeout waiting for animation");
 696             }
 697         } catch (InterruptedException ex) {
 698             fail("Unexpected exception: " + ex);
 699         }
 700 
 701         final Throwable t = error.get();
 702         if (t != null) {
 703             throw t;
 704         }
 705 
 706         assertFalse(tmpStage1.isShowing());
 707     }
 708 
 709     // Verify that Alert.showAndWait throws an exception if called from an
 710     // animation timeline.
 711     @Test
 712     public void testTimelineDialog() throws Throwable {
 713         ensureTest1();
 714 
 715         final CountDownLatch animationDone = new CountDownLatch(1);
 716         final AtomicReference<Throwable> error = new AtomicReference<>(null);
 717 
 718         KeyFrame kf = new KeyFrame(Duration.millis(200), e -> {
 719             Alert alert = null;
 720             try {
 721                 alert = new Alert(Alert.AlertType.INFORMATION);
 722                 assertFalse(alert.isShowing());
 723                 try {
 724                     alert.showAndWait();
 725                     fail("Did not get expected exception from showAndWait");
 726                 } catch (IllegalStateException ex) {
 727                     // Good
 728                 }
 729                 assertFalse(alert.isShowing());
 730             } catch (Throwable t) {
 731                 error.set(t);
 732                 try {
 733                     if (alert.isShowing()) {
 734                         alert.close();
 735                     }
 736                 } catch (RuntimeException ex) {}
 737             }
 738             animationDone.countDown();
 739         });
 740         Timeline timeline = new Timeline(kf);
 741         timeline.play();
 742 
 743         try {
 744             if (!animationDone.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
 745                 fail("Timeout waiting for animation");
 746             }
 747         } catch (InterruptedException ex) {
 748             fail("Unexpected exception: " + ex);
 749         }
 750 
 751         final Throwable t = error.get();
 752         if (t != null) {
 753             throw t;
 754         }
 755     }
 756 
 757     // Verify that printing throws an exception if called from an
 758     // animation timeline.
 759     @Test
 760     public void testTimelinePrint() throws Throwable {
 761         assumeNotNull(PrinterJob.createPrinterJob());
 762 
 763         ensureTest1();
 764 
 765         final CountDownLatch animationDone = new CountDownLatch(1);
 766         final AtomicReference<Throwable> error = new AtomicReference<>(null);
 767 
 768         KeyFrame kf = new KeyFrame(Duration.millis(200), e -> {
 769             try {
 770                 PrinterJob job = PrinterJob.createPrinterJob();
 771                 try {
 772                     job.showPrintDialog(myApp.primaryStage);
 773                     fail("Did not get expected exception from showPrintDialog");
 774                 } catch (IllegalStateException ex) {
 775                     // Good
 776                 }
 777                 try {
 778                     job.showPageSetupDialog(myApp.primaryStage);
 779                     fail("Did not get expected exception from showPageSetupDialog");
 780                 } catch (IllegalStateException ex) {
 781                     // Good
 782                 }
 783                 try {
 784                     Rectangle rect = new Rectangle(200, 100, Color.GREEN);
 785                     job.printPage(rect);
 786                     fail("Did not get expected exception from printPage");
 787                 } catch (IllegalStateException ex) {
 788                     // Good
 789                 }
 790             } catch (Throwable t) {
 791                 error.set(t);
 792             }
 793             animationDone.countDown();
 794         });
 795         Timeline timeline = new Timeline(kf);
 796         timeline.play();
 797 
 798         try {
 799             if (!animationDone.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
 800                 fail("Timeout waiting for animation");
 801             }
 802         } catch (InterruptedException ex) {
 803             fail("Unexpected exception: " + ex);
 804         }
 805 
 806         final Throwable t = error.get();
 807         if (t != null) {
 808             throw t;
 809         }
 810     }
 811 
 812 }