1 /* 2 * Copyright (c) 2013, 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 package testharness; 26 27 import javafx.animation.AnimationTimer; 28 import javafx.application.Application; 29 import javafx.application.Platform; 30 import javafx.scene.Scene; 31 import javafx.scene.paint.Color; 32 import javafx.stage.Stage; 33 import javafx.stage.StageStyle; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.concurrent.CountDownLatch; 37 import java.util.concurrent.TimeUnit; 38 import com.sun.glass.ui.Robot; 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 util.Util; 45 import static org.junit.Assert.assertEquals; 46 import static org.junit.Assert.assertNotNull; 47 import static org.junit.Assert.assertTrue; 48 import static util.Util.TIMEOUT; 49 50 /** 51 * Common base class for testing snapshot. 52 */ 53 public abstract class VisualTestBase { 54 55 // Used to launch the application before running any test 56 private static final CountDownLatch launchLatch = new CountDownLatch(1); 57 // Singleton Application instance 58 private static MyApp myApp; 59 // Scene instances used for testing 60 private List<Stage> stages = new ArrayList<>(); 61 62 // Glass Robot instance 63 Robot robot; 64 65 // Application class. An instance is created and initialized before running 66 // the first test, and it lives through the execution of all tests. 67 public static class MyApp extends Application { 68 69 @Override 70 public void init() { 71 VisualTestBase.myApp = this; 72 } 73 74 @Override 75 public void start(Stage primaryStage) throws Exception { 76 Platform.setImplicitExit(false); 77 assertTrue(Platform.isFxApplicationThread()); 78 assertNotNull(primaryStage); 79 80 launchLatch.countDown(); 81 } 82 } 83 84 @BeforeClass 85 public static void doSetupOnce() { 86 // Start the Application 87 new Thread(() -> Application.launch(MyApp.class, (String[]) null)).start(); 88 89 try { 90 if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { 91 throw new AssertionFailedError("Timeout waiting for Application to launch"); 92 } 93 } catch (InterruptedException ex) { 94 AssertionFailedError err = new AssertionFailedError("Unexpected exception"); 95 err.initCause(ex); 96 throw err; 97 } 98 99 assertEquals(0, launchLatch.getCount()); 100 } 101 102 @AfterClass 103 public static void doTeardownOnce() { 104 Platform.exit(); 105 } 106 107 @Before 108 public void doSetup() { 109 runAndWait(() -> robot = com.sun.glass.ui.Application.GetApplication().createRobot()); 110 } 111 112 @After 113 public void doTeardown() { 114 runAndWait(() -> { 115 if (!stages.isEmpty()) { 116 for (final Stage stage : stages) { 117 if (stage.isShowing()) { 118 stage.hide(); 119 } 120 } 121 stages.clear(); 122 } 123 }); 124 } 125 126 protected void runAndWait(final Runnable r) { 127 Util.runAndWait(r); 128 } 129 130 // This must be called on the FX app thread 131 protected Stage getStage() { 132 Stage stage = new Stage(); 133 // Undecorated stage to workaround RT-39904 134 stage.initStyle(StageStyle.UNDECORATED); 135 stages.add(stage); 136 return stage; 137 } 138 139 protected void sleep(long millis) { 140 try { 141 Thread.sleep(millis); 142 } catch (InterruptedException ex) { 143 throw new AssertionFailedError("Unexpected exception: " + ex); 144 } 145 } 146 147 // This must be called on the FX app thread 148 protected Color getColor(Scene scene, int x, int y) { 149 x += scene.getX() + scene.getWindow().getX(); 150 y += scene.getY() + scene.getWindow().getY(); 151 int pixColor = robot.getPixelColor(x, y); 152 int a = (pixColor >> 24) & 0xff; 153 int r = (pixColor >> 16) & 0xff; 154 int g = (pixColor >> 8) & 0xff; 155 int b = pixColor & 0xff; 156 157 Color color = Color.rgb(r, g, b, (double)a / 255.0); 158 return color; 159 } 160 161 private static String colorToString(Color c) { 162 int r = (int)(c.getRed() * 255.0); 163 int g = (int)(c.getGreen() * 255.0); 164 int b = (int)(c.getBlue() * 255.0); 165 int a = (int)(c.getOpacity() * 255.0); 166 return "rgba(" + r + "," + g + "," + b + "," + a + ")"; 167 } 168 169 protected void assertColorEquals(Color expected, Color actual, double delta) { 170 if (!testColorEquals(expected, actual, delta)) { 171 throw new AssertionFailedError("expected:" + colorToString(expected) 172 + " but was:" + colorToString(actual)); 173 } 174 } 175 176 protected boolean testColorEquals(Color expected, Color actual, double delta) { 177 double deltaRed = Math.abs(expected.getRed() - actual.getRed()); 178 double deltaGreen = Math.abs(expected.getGreen() - actual.getGreen()); 179 double deltaBlue = Math.abs(expected.getBlue() - actual.getBlue()); 180 double deltaOpacity = Math.abs(expected.getOpacity() - actual.getOpacity()); 181 return (deltaRed <= delta && deltaGreen <= delta && deltaBlue <= delta && deltaOpacity <= delta); 182 } 183 184 protected void assertColorDoesNotEqual(Color notExpected, Color actual, double delta) { 185 double deltaRed = Math.abs(notExpected.getRed() - actual.getRed()); 186 double deltaGreen = Math.abs(notExpected.getGreen() - actual.getGreen()); 187 double deltaBlue = Math.abs(notExpected.getBlue() - actual.getBlue()); 188 double deltaOpacity = Math.abs(notExpected.getOpacity() - actual.getOpacity()); 189 if (deltaRed < delta && deltaGreen < delta && deltaBlue < delta && deltaOpacity < delta) { 190 throw new AssertionFailedError("not expected:" + colorToString(notExpected) 191 + " but was:" + colorToString(actual)); 192 } 193 } 194 195 private AnimationTimer timer; 196 197 private void frameWait(int n) { 198 final CountDownLatch frameCounter = new CountDownLatch(n); 199 Platform.runLater(() -> { 200 timer = new AnimationTimer() { 201 @Override public void handle(long l) { 202 frameCounter.countDown(); 203 } 204 }; 205 timer.start(); 206 }); 207 208 try { 209 frameCounter.await(); 210 } catch (InterruptedException ex) { 211 throw new AssertionFailedError("Unexpected exception: " + ex); 212 } finally { 213 runAndWait(() -> { 214 if (timer != null) { 215 timer.stop(); 216 } 217 }); 218 } 219 } 220 221 // Waits until the fist frame is rendered after the stage has been shown 222 protected void waitFirstFrame() { 223 // This is a temporary workaround until RT-28683 is implemented 224 frameWait(100); 225 } 226 227 // Waits until the frame containing the current state of the scene has 228 // been rendered 229 protected void waitNextFrame() { 230 // This is a temporary workaround until RT-28683 is implemented 231 // Need to wait for the current frame in process and then the next frame 232 // However, we get many intermittent failures with 2 and a very few with 233 // 3, so we will wait for 5 frames. 234 frameWait(5); 235 } 236 237 }