1 /* 2 * Copyright (c) 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 com.sun.javafx; 27 28 import com.sun.javafx.stage.StageHelper; 29 import javafx.animation.AnimationTimer; 30 import javafx.application.Application; 31 import javafx.application.Platform; 32 import javafx.stage.Stage; 33 import junit.framework.Assert; 34 import org.junit.internal.runners.statements.Fail; 35 import org.junit.internal.runners.statements.InvokeMethod; 36 import org.junit.rules.MethodRule; 37 import org.junit.runners.model.FrameworkMethod; 38 import org.junit.runners.model.Statement; 39 40 import java.lang.ref.Reference; 41 import java.lang.reflect.Method; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 import java.util.Map; 46 import java.util.concurrent.CountDownLatch; 47 import java.util.concurrent.atomic.AtomicReference; 48 import java.util.function.Function; 49 import java.util.stream.Collector; 50 import java.util.stream.Collectors; 51 import java.util.stream.Stream; 52 53 public class FXUnit implements MethodRule { 54 55 private static CountDownLatch startupLatch = new CountDownLatch(1); 56 57 public static class TestApplication extends Application { 58 public TestApplication() { } 59 @Override 60 public void start(Stage primaryStage) throws Exception { 61 Platform.setImplicitExit(false); 62 primaryStage.close(); 63 startupLatch.countDown(); 64 } 65 } 66 67 public static class AsyncException extends RuntimeException { 68 private AsyncException(Exception e) { 69 super(); 70 initCause(e); 71 } 72 } 73 74 private class InvokeAndWaitStatement extends Statement { 75 private Statement statement; 76 private InvokeAndWaitStatement(Statement statement) { 77 this.statement = statement; 78 } 79 80 @Override 81 public void evaluate() throws Throwable { 82 invokeAndWait(statement); 83 } 84 } 85 86 public static interface TestRunnable { 87 public void run() throws Exception; 88 } 89 90 public FXUnit() { 91 synchronized (FXUnit.class) { 92 startToolkit(); 93 } 94 } 95 96 97 @Override 98 public Statement apply(Statement statement, FrameworkMethod frameworkMethod, 99 Object test) { 100 List<Statement> befores = new ArrayList<>(); 101 List<Statement> afters = new ArrayList<>(); 102 try { 103 Method[] methods = test.getClass().getMethods(); 104 for (Method m : methods) { 105 FXBefore before = m.getAnnotation(FXBefore.class); 106 if (before != null) { 107 InvokeMethod invoker = new InvokeMethod( 108 new FrameworkMethod(m), test); 109 if (before.value().equals(FXThread.MAIN)) { 110 befores.add(invoker); 111 } else { 112 befores.add(new InvokeAndWaitStatement(invoker)); 113 } 114 } 115 FXAfter after = m.getAnnotation(FXAfter.class); 116 if (after != null) { 117 InvokeMethod invoker = new InvokeMethod( 118 new FrameworkMethod(m), test); 119 if (after.value().equals(FXThread.MAIN)) { 120 afters.add(invoker); 121 } else { 122 afters.add(new InvokeAndWaitStatement(invoker)); 123 } 124 } 125 } 126 } catch (Exception e) { 127 return new Fail(e); 128 } 129 FXTestThread threadAnnotation = frameworkMethod.getAnnotation(FXTestThread.class); 130 FXThread testThread = threadAnnotation == null 131 ? FXThread.APPLICATION 132 : threadAnnotation.value(); 133 Statement newStatement = testThread.equals(FXThread.MAIN) 134 ? statement 135 : new InvokeAndWaitStatement(statement); 136 return new Statement() { 137 @Override 138 public void evaluate() throws Throwable { 139 for (Statement s : befores) { 140 s.evaluate(); 141 } 142 newStatement.evaluate(); 143 for (Statement s : afters) { 144 s.evaluate(); 145 } 146 } 147 }; 148 } 149 150 public synchronized void startToolkit() { 151 if (startupLatch.getCount() != 0) { 152 if (Boolean.getBoolean("headless")) { 153 System.getProperty("glass.platform", "Monocle"); 154 System.getProperty("monocle.platform", "Headless"); 155 System.getProperty("prism.order", "sw"); 156 } 157 new Thread(() -> Application.launch(TestApplication.class)).start(); 158 try { 159 startupLatch.await(); 160 } catch (InterruptedException e) { 161 Assert.fail("Interrupted when initializing toolkit"); 162 } 163 } else { 164 invokeAndWait(() -> { 165 List<Stage> stages = new ArrayList<>(); 166 stages.addAll(StageHelper.getStages()); 167 stages.forEach((stage) -> stage.close()); 168 }); 169 waitForNextPulse(); 170 } 171 } 172 173 public void waitForNextPulse() { 174 CountDownLatch pulseLatch = new CountDownLatch(1); 175 Platform.runLater(() -> new AnimationTimer() { 176 @Override 177 public void handle(long now) { 178 pulseLatch.countDown(); 179 stop(); 180 } 181 }.start()); 182 try { 183 pulseLatch.await(); 184 } catch (InterruptedException e) { 185 Assert.fail("Interrupted when waiting for the next pulse"); 186 } 187 } 188 189 public void invokeAndWait(TestRunnable runnable) { 190 invokeAndWait(new Statement() { 191 @Override 192 public void evaluate() throws Throwable { 193 runnable.run(); 194 } 195 }); 196 } 197 198 private void invokeAndWait(Statement statement) { 199 if (Platform.isFxApplicationThread()) { 200 try { 201 statement.evaluate(); 202 } catch (RuntimeException | Error e) { 203 throw e; 204 } catch (Throwable t) { 205 throw new AsyncException((Exception) t); 206 } 207 } else { 208 final CountDownLatch runLatch = new CountDownLatch(1); 209 final AtomicReference<Throwable> throwable = new AtomicReference<>(); 210 Platform.runLater(() -> { 211 try { 212 try { 213 statement.evaluate(); 214 } catch (Throwable t) { 215 throwable.set(t); 216 } 217 } finally { 218 runLatch.countDown(); 219 } 220 }); 221 try { 222 runLatch.await(); 223 } catch (InterruptedException e) { 224 throw new AsyncException(e); 225 } 226 Throwable t = throwable.get(); 227 if (t != null) { 228 if (t instanceof Error) { 229 throw (Error) t; 230 } else if (t instanceof RuntimeException) { 231 throw (RuntimeException) t; 232 } else if (t instanceof Exception) { 233 throw new AsyncException((Exception) t); 234 } 235 } 236 } 237 } 238 239 } 240