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 }