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