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 }