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 }