1 /*
   2  * Copyright (c) 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 java.util.*;
  29 import java.util.concurrent.CountDownLatch;
  30 import java.util.concurrent.TimeUnit;
  31 import java.util.concurrent.atomic.AtomicBoolean;
  32 import java.util.concurrent.atomic.AtomicLong;
  33 import java.util.concurrent.atomic.AtomicReference;
  34 import javafx.animation.KeyFrame;
  35 import javafx.animation.Timeline;
  36 import javafx.application.Application;
  37 import javafx.application.Platform;
  38 import javafx.print.PrinterJob;
  39 import javafx.scene.Group;
  40 import javafx.scene.Scene;
  41 import javafx.scene.control.Alert;
  42 import javafx.scene.paint.Color;
  43 import javafx.scene.shape.Rectangle;
  44 import javafx.stage.Stage;
  45 import javafx.util.Duration;
  46 import junit.framework.AssertionFailedError;
  47 import org.junit.After;
  48 import org.junit.AfterClass;
  49 import org.junit.Before;
  50 import org.junit.BeforeClass;
  51 import org.junit.Test;
  52 import org.junit.runner.RunWith;
  53 import org.junit.runners.Parameterized;
  54 import org.junit.runners.Parameterized.Parameters;
  55 import test.util.Util;
  56 
  57 import static org.junit.Assert.*;
  58 import static org.junit.Assume.*;
  59 import static test.util.Util.TIMEOUT;
  60 
  61 /**
  62  * Test program for nested event loop functionality.
  63  */
  64 public class NestedEventLoopTest {
  65 
  66     // Used to launch the application before running any test
  67     private static final CountDownLatch launchLatch = new CountDownLatch(1);
  68 
  69     // Singleton Application instance
  70     private static MyApp myApp;
  71 
  72     // Application class. An instance is created and initialized before running
  73     // the first test, and it lives through the execution of all tests.
  74     public static class MyApp extends Application {
  75         private Stage primaryStage;
  76 
  77         @Override public void init() {
  78             NestedEventLoopTest.myApp = this;
  79         }
  80 
  81         @Override public void start(Stage primaryStage) throws Exception {
  82             primaryStage.setTitle("Primary stage");
  83             Group root = new Group();
  84             Scene scene = new Scene(root);
  85             scene.setFill(Color.LIGHTYELLOW);
  86             primaryStage.setScene(scene);
  87             primaryStage.setX(0);
  88             primaryStage.setY(0);
  89             primaryStage.setWidth(210);
  90             primaryStage.setHeight(180);
  91 
  92             this.primaryStage = primaryStage;
  93             launchLatch.countDown();
  94         }
  95     }
  96 
  97     @BeforeClass
  98     public static void setupOnce() {
  99         // Start the Application
 100         new Thread(() -> Application.launch(MyApp.class, (String[])null)).start();
 101 
 102         try {
 103             if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) {
 104                 throw new AssertionFailedError("Timeout waiting for Application to launch");
 105             }
 106         } catch (InterruptedException ex) {
 107             AssertionFailedError err = new AssertionFailedError("Unexpected exception");
 108             err.initCause(ex);
 109             throw err;
 110         }
 111     }
 112 
 113     @AfterClass
 114     public static void teardownOnce() {
 115         Platform.exit();
 116     }
 117 
 118     // Verify that we cannot enter a nested event loop on a thread other than
 119     // the FX Application thread
 120     @Test (expected=IllegalStateException.class)
 121     public void testMustRunOnAppThread() {
 122         assertFalse(Platform.isFxApplicationThread());
 123         Platform.enterNestedEventLoop(new Object());
 124     }
 125 
 126     // Verify that we can enter and exit a nested event loop
 127     @Test public void testCanEnterAndExitNestedEventLoop() {
 128         final long key = 1024L;
 129         final long result = 2048L;
 130         final AtomicLong returnedValue = new AtomicLong();
 131 
 132         Util.runAndWait(
 133                 () -> {
 134                     assertFalse(Platform.isNestedLoopRunning());
 135                     Long actual = (Long) Platform.enterNestedEventLoop(key);
 136                     returnedValue.set(actual);
 137                 },
 138                 () -> {
 139                     assertTrue(Platform.isNestedLoopRunning());
 140                     Platform.exitNestedEventLoop(key, result);
 141                 },
 142                 () -> {
 143                     assertFalse(Platform.isNestedLoopRunning());
 144                     assertEquals(result, returnedValue.get());
 145                 }
 146         );
 147     }
 148 
 149     // Verify that we cannot enter a nested event loop with the same key twice
 150     @Test (expected=IllegalArgumentException.class)
 151     public void testUniqueKeyRequired() {
 152         final Object key = new Object();
 153         Util.runAndWait(
 154                 () -> Platform.enterNestedEventLoop(key),
 155                 () -> Platform.enterNestedEventLoop(key),
 156                 () -> Platform.exitNestedEventLoop(key, null)
 157         );
 158     }
 159 
 160     // Verify that we cannot enter a nested event loop with a null key
 161     @Test (expected=NullPointerException.class)
 162     public void testNonNullKeyRequired() {
 163         Util.runAndWait(
 164                 () -> Platform.enterNestedEventLoop(null)
 165         );
 166     }
 167 
 168     // Verify that we cannot exit a nested event loop with a null key
 169     @Test (expected=NullPointerException.class)
 170     public void testNonNullExitKeyRequired() {
 171         Util.runAndWait(
 172                 () -> Platform.enterNestedEventLoop("validKey"),
 173                 () -> Platform.exitNestedEventLoop(null, null),
 174                 () -> Platform.exitNestedEventLoop("validKey", null)
 175         );
 176     }
 177 
 178     // Verify that we cannot exit a nested event loop with a key that has not been used
 179     @Test (expected=IllegalArgumentException.class)
 180     public void testExitLoopKeyHasBeenRegistered() {
 181         Util.runAndWait(
 182                 () -> Platform.enterNestedEventLoop("validKey"),
 183                 () -> Platform.exitNestedEventLoop("invalidKey", null),
 184                 () -> Platform.exitNestedEventLoop("validKey", null)
 185         );
 186     }
 187 
 188     // Verify that we can enter and exit multiple nested event loops, in the order they are started
 189     @Test public void testCanEnterMultipleNestedLoops_andExitInOrder() {
 190         final long key1 = 1024L;
 191         final long key2 = 1025L;
 192         final long result1 = 2048L;
 193         final long result2 = 2049L;
 194         final AtomicLong returnedValue1 = new AtomicLong();
 195         final AtomicLong returnedValue2 = new AtomicLong();
 196 
 197         Util.runAndWait(
 198                 () -> {
 199                     // enter loop one
 200                     assertFalse(Platform.isNestedLoopRunning());
 201                     Long actual = (Long) Platform.enterNestedEventLoop(key1);
 202                     returnedValue1.set(actual);
 203                 },
 204                 () -> {
 205                     // enter loop two
 206                     assertTrue(Platform.isNestedLoopRunning());
 207                     Long actual = (Long) Platform.enterNestedEventLoop(key2);
 208                     returnedValue2.set(actual);
 209                 },
 210                 () -> {
 211                     // exit loop two
 212                     assertTrue(Platform.isNestedLoopRunning());
 213                     Platform.exitNestedEventLoop(key2, result2);
 214                 },
 215                 () -> {
 216                     // check loop two is done
 217                     assertTrue(Platform.isNestedLoopRunning());
 218                     assertEquals(result2, returnedValue2.get());
 219                 },
 220                 () -> {
 221                     // exit loop one
 222                     assertTrue(Platform.isNestedLoopRunning());
 223                     Platform.exitNestedEventLoop(key1, result1);
 224                 },
 225                 () -> {
 226                     // check loop one is done
 227                     assertFalse(Platform.isNestedLoopRunning());
 228                     assertEquals(result1, returnedValue1.get());
 229                 }
 230         );
 231     }
 232 }