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