1 /* 2 * Copyright (c) 2016, 2017, 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.com.sun.marlin; 27 28 import java.util.Locale; 29 import java.util.concurrent.CountDownLatch; 30 import java.util.concurrent.TimeUnit; 31 import java.util.logging.Handler; 32 import java.util.logging.Level; 33 import java.util.logging.LogRecord; 34 import java.util.logging.Logger; 35 36 import javafx.animation.KeyFrame; 37 import javafx.animation.KeyValue; 38 import javafx.animation.Timeline; 39 import javafx.application.Application; 40 import javafx.application.Platform; 41 import javafx.beans.property.DoubleProperty; 42 import javafx.beans.property.SimpleDoubleProperty; 43 import javafx.geometry.Bounds; 44 import javafx.scene.Group; 45 import javafx.scene.Scene; 46 import javafx.scene.paint.Color; 47 import javafx.scene.shape.SVGPath; 48 import javafx.stage.Stage; 49 import javafx.util.Duration; 50 51 import junit.framework.AssertionFailedError; 52 import org.junit.AfterClass; 53 import org.junit.Assert; 54 import static org.junit.Assert.assertEquals; 55 import org.junit.BeforeClass; 56 import org.junit.Test; 57 58 import static test.util.Util.TIMEOUT; 59 60 /** 61 * @test 62 * @bug 8170140 63 * @summary Check the rendering anomaly with MarlinFX renderer 64 */ 65 public class QPathTest { 66 67 private final static double SCALE = 2.0; 68 69 private final static long MAX_DURATION = 3000 * 1000 * 1000L; // 3s 70 71 // Used to launch the application before running any test 72 private static final CountDownLatch launchLatch = new CountDownLatch(1); 73 74 // Singleton Application instance 75 static MyApp myApp; 76 77 static boolean doChecksFailed = false; 78 79 static { 80 Locale.setDefault(Locale.US); 81 82 // initialize j.u.l Looger: 83 final Logger log = Logger.getLogger("prism.marlin"); 84 log.addHandler(new Handler() { 85 @Override 86 public void publish(LogRecord record) { 87 final Throwable th = record.getThrown(); 88 // detect any Throwable: 89 if (th != null) { 90 System.out.println("Test failed:\n" + record.getMessage()); 91 th.printStackTrace(System.out); 92 93 doChecksFailed = true; 94 95 throw new RuntimeException("Test failed: ", th); 96 } 97 } 98 99 @Override 100 public void flush() { 101 } 102 103 @Override 104 public void close() { 105 } 106 }); 107 108 // enable Marlin logging & internal checks: 109 System.setProperty("prism.marlinrasterizer", "true"); 110 System.setProperty("prism.marlin.log", "true"); 111 System.setProperty("prism.marlin.useLogger", "true"); 112 System.setProperty("prism.marlin.doChecks", "true"); 113 } 114 115 private CountDownLatch latch = new CountDownLatch(1); 116 117 // Application class. An instance is created and initialized before running 118 // the first test, and it lives through the execution of all tests. 119 public static class MyApp extends Application { 120 121 Stage stage = null; 122 123 public MyApp() { 124 super(); 125 } 126 127 @Override 128 public void init() { 129 QPathTest.myApp = this; 130 } 131 132 @Override 133 public void start(Stage primaryStage) throws Exception { 134 this.stage = primaryStage; 135 launchLatch.countDown(); 136 } 137 } 138 139 @BeforeClass 140 public static void setupOnce() { 141 // Start the Application 142 new Thread(() -> Application.launch(MyApp.class, (String[]) null)).start(); 143 144 try { 145 if (!launchLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)) { 146 throw new AssertionFailedError("Timeout waiting for Application to launch"); 147 } 148 } catch (InterruptedException ex) { 149 AssertionFailedError err = new AssertionFailedError("Unexpected exception"); 150 err.initCause(ex); 151 throw err; 152 } 153 154 assertEquals(0, launchLatch.getCount()); 155 } 156 157 @AfterClass 158 public static void teardownOnce() { 159 Platform.exit(); 160 } 161 162 @Test(timeout = 10000) 163 public void TestBug() { 164 Platform.runLater(() -> { 165 SVGPath path = new SVGPath(); 166 String svgpath = readPath(); 167 path.setContent(svgpath); 168 169 Scene scene = new Scene(new Group(path), 400, 400, Color.WHITE); 170 myApp.stage.setScene(scene); 171 myApp.stage.show(); 172 173 DoubleProperty rscale = new SimpleDoubleProperty(SCALE); 174 myApp.stage.renderScaleXProperty().bind(rscale); 175 myApp.stage.renderScaleYProperty().bind(rscale); 176 177 double scw = scene.getWidth(); 178 double sch = scene.getHeight(); 179 Bounds pathbounds = path.getBoundsInParent(); 180 double pathXoff = -pathbounds.getMinX(); 181 double pathYoff = -pathbounds.getMinY(); 182 double bounceW = scw - pathbounds.getWidth(); 183 double bounceH = sch - pathbounds.getHeight(); 184 double Xrate = (1.0 + Math.random()) / 2.0; 185 double Yrate = (1.0 + Math.random()) / 2.0; 186 187 DoubleProperty prop = new SimpleDoubleProperty(); 188 189 final long start = System.nanoTime(); 190 191 prop.addListener((Observable) -> { 192 if (doChecksFailed || System.nanoTime() - start > MAX_DURATION) { 193 latch.countDown(); 194 myApp.stage.close(); 195 } 196 double v = prop.doubleValue(); 197 double x = Math.abs((((v * Xrate) % 2.0) - 1.0) * bounceW); 198 double y = Math.abs((((v * Yrate) % 2.0) - 1.0) * bounceH); 199 path.setTranslateX(pathXoff + x); 200 path.setTranslateY(pathYoff + y); 201 path.setContent(null); 202 path.setContent(svgpath); 203 }); 204 int bignum = 1000000; 205 KeyValue kv = new KeyValue(prop, bignum); 206 KeyFrame kf = new KeyFrame(Duration.seconds(bignum), kv); 207 Timeline t = new Timeline(kf); 208 t.setCycleCount(Timeline.INDEFINITE); 209 t.play(); 210 }); 211 try { 212 latch.await(); 213 } catch (InterruptedException ie) { 214 Logger.getLogger(QPathTest.class.getName()).log(Level.SEVERE, "interrupted", ie); 215 } 216 Assert.assertFalse("DoChecks detected a problem.", doChecksFailed); 217 } 218 219 static String readPath() { 220 return "M54.589844,86.230469 C27.929688,86.230469 10.546875,107.714844 10.546875,140.722656 C10.546875,173.925781 " 221 + "27.734375,195.214844 54.589844,195.214844 C69.433594,195.214844 80.761719,189.062500 87.500000,177.539063 " 222 + "L89.062500,177.539063 L89.062500,228.515625 L106.054688,228.515625 L106.054688,88.085938 L89.843750,88.085938 " 223 + "L89.843750,105.664063 L88.281250,105.664063 C82.031250,93.847656 68.945313,86.230469 54.589844,86.230469 Z " 224 + "M58.398438,180.078125 C39.257813,180.078125 27.929688,165.429688 27.929688,140.722656 C27.929688,116.113281 " 225 + "39.355469,101.367188 58.496094,101.367188 C77.539063,101.367188 89.550781,116.601563 89.550781,140.722656 " 226 + "C89.550781,164.941406 77.636719,180.078125 58.398438,180.078125 Z "; 227 } 228 }