/* * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/* * @test * @bug 4172661 8176501 * @summary Tests all public methods of Path2D classes on all 3 variants * Path2D.Float, Path2D.Double, and GeneralPath. * REMIND: Note that the hit testing tests will fail * occasionally due to precision bugs in the various hit * testing methods in the geometry classes. * (Failure rates vary from 1 per 100 runs to 1 per thousands). * See bug 6396047 to track progress on these failures.
*/
publicstatic Shape EmptyShapeNonZero = new EmptyShape(WIND_NON_ZERO); publicstatic Shape EmptyShapeEvenOdd = new EmptyShape(WIND_EVEN_ODD);
// Note: We pick a shape that is not anywhere near any of // our test shapes so that the Path2D does not try to collapse // out the connecting segment - an optimization that is too // difficult to account for in the AppendedShape code. publicstatic Shape AppendShape = new Arc2D.Double(1000, 1000, 40, 40,
Math.PI/4, Math.PI,
Arc2D.CHORD);
publicstatic AffineTransform makeAT() {
AffineTransform at = new AffineTransform();
at.scale(0.66, 0.23);
at.rotate(Math.toRadians(35.0));
at.shear(0.78, 1.32); return at;
}
Random random;
public UnitTest(long randomSeed) { this.random = new Random(randomSeed);
TestShapes = new Shape[] {
EmptyShapeNonZero,
EmptyShapeEvenOdd, new Line2D.Double(), new Line2D.Double(rpc(), rpc(), rpc(), rpc()), new Line2D.Double(rnc(), rnc(), rnc(), rnc()), new Rectangle2D.Double(), new Rectangle2D.Double(rpc(), rpc(), -1, -1), new Rectangle2D.Double(rpc(), rpc(), rd(), rd()), new Rectangle2D.Double(rnc(), rnc(), rd(), rd()), new Ellipse2D.Double(), new Ellipse2D.Double(rpc(), rpc(), -1, -1), new Ellipse2D.Double(rpc(), rpc(), rd(), rd()), new Ellipse2D.Double(rnc(), rnc(), rd(), rd()), new Arc2D.Double(Arc2D.OPEN), new Arc2D.Double(Arc2D.CHORD), new Arc2D.Double(Arc2D.PIE), new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.OPEN), new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.CHORD), new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.PIE), new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.OPEN), new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.CHORD), new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.PIE), new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.OPEN), new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.CHORD), new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.PIE), new RoundRectangle2D.Double(), new RoundRectangle2D.Double(rpc(), rpc(), -1, -1, ra(), ra()), new RoundRectangle2D.Double(rpc(), rpc(), rd(), rd(), ra(), ra()), new RoundRectangle2D.Double(rnc(), rnc(), rd(), rd(), ra(), ra()), new QuadCurve2D.Double(), new QuadCurve2D.Double(rpc(), rpc(), rpc(), rpc(), rpc(), rpc()), new QuadCurve2D.Double(rnc(), rnc(), rnc(), rnc(), rnc(), rnc()), new CubicCurve2D.Double(), new CubicCurve2D.Double(rpc(), rpc(), rpc(), rpc(),
rpc(), rpc(), rpc(), rpc()), new CubicCurve2D.Double(rnc(), rnc(), rnc(), rnc(),
rnc(), rnc(), rnc(), rnc()),
makeGeneralPath(WIND_NON_ZERO, 1.0),
makeGeneralPath(WIND_EVEN_ODD, 1.0),
makeGeneralPath(WIND_NON_ZERO, -1.0),
makeGeneralPath(WIND_EVEN_ODD, -1.0),
makeJDK8176501(),
// this shape has a special property: some coefficients to the t^3 term // are *nearly* zero. And analytically they should be zero, but machine // error prevented it. In these cases cubic polynomials should degenerate // into quadratic polynomials, but because the coefficient is not exactly // zero that may not always be handled correctly:
AffineTransform.getRotateInstance(Math.PI / 4).createTransformedShape( new Ellipse2D.Float(0, 0, 100, 100))
};
int types[] = newint[100]; int i = 0;
types[i++] = PathIterator.SEG_MOVETO;
types[i++] = PathIterator.SEG_LINETO;
types[i++] = PathIterator.SEG_QUADTO;
types[i++] = PathIterator.SEG_CUBICTO;
types[i++] = PathIterator.SEG_CLOSE; int shortlen = i; int prevt = types[i-1]; while (i < types.length) { int t; do {
t = (int) (random.nextDouble() * 5);
} while (t == prevt &&
(t == PathIterator.SEG_MOVETO ||
t == PathIterator.SEG_CLOSE));
types[i++] = t;
prevt = t;
}
int numcoords = 0; int numshortcoords = 0; for (i = 0; i < types.length; i++) { if (i == shortlen) {
numshortcoords = numcoords;
}
numcoords += CoordsForType[types[i]];
} double coords[] = newdouble[numcoords]; for (i = 0; i < coords.length; i++) {
coords[i] = rpc();
}
ShortSampleNonZero = new SampleShape(WIND_NON_ZERO,
types, coords,
shortlen, numshortcoords);
ShortSampleEvenOdd = new SampleShape(WIND_EVEN_ODD,
types, coords,
shortlen, numshortcoords);
LongSampleNonZero = new SampleShape(WIND_NON_ZERO,
types, coords,
types.length, numcoords);
LongSampleEvenOdd = new SampleShape(WIND_EVEN_ODD,
types, coords,
types.length, numcoords);
}
/** * JDK-8176501 focused on a shape whose bounds included a lot of dead space. * This recreates that shape, and the unit test testGetBounds2D checks the * accuracy of {@link Shape#getBounds2D()}
*/ publicstatic Path2D makeJDK8176501() {
Path2D.Double path = new Path2D.Double();
path.moveTo(40, 140);
path.curveTo(40, 60, 160, 60, 160, 140);
path.curveTo(160, 220, 40, 220, 40, 140);
path.closePath(); return path;
}
// Due to odd issues with the sizes of errors when the values // being manipulated are near zero, we try to avoid values // near zero by ensuring that both the rpc (positive coords) // stay away from zero and also by ensuring that the rpc+rd // (positive coords + dimensions) stay away from zero. We // also ensure that rnc+rd (negative coords + dimension) stay // suitably negative without approaching zero.
// Random positive coordinate (10 -> 110) // rpc + rd gives a total range of (30 -> 170) publicdouble rpc() { return (random.nextDouble() * 100.0) + 10.0;
}
// Random negative coordinate (-200 -> -100) // rnc + rd gives a total range of (-180 -> -40) publicdouble rnc() { return (random.nextDouble() * 100.0) - 200.0;
}
if (tContainsXY != uContainsXY) { thrownew RuntimeException("contains(x,y) "+ "does not match utility");
} if (tContainsXYWH != uContainsXYWH) { thrownew RuntimeException("contains(x,y,w,h) "+ "does not match utility");
} if (tIntersectsXYWH != uIntersectsXYWH) { thrownew RuntimeException("intersects(x,y,w,h) "+ "does not match utility");
}
// Make rect slightly smaller to be more conservative for rContains double srx = rx + 0.1; double sry = ry + 0.1; double srw = rw - 0.2; double srh = rh - 0.2;
Rectangle2D srect = new Rectangle2D.Double(srx, sry, srw, srh); // Make rect slightly larger to be more liberal for rIntersects double lrx = rx - 0.1; double lry = ry - 0.1; double lrw = rw + 0.2; double lrh = rh + 0.2;
Rectangle2D lrect = new Rectangle2D.Double(lrx, lry, lrw, lrh);
if (srect.isEmpty()) { thrownew InternalError("smaller rect too small (empty)");
} if (!lrect.contains(rect)) { thrownew InternalError("test rect not inside larger rect!");
} if (!rect.contains(srect)) { thrownew InternalError("smaller rect not inside test rect!");
}
if (sref instanceof SampleShape ||
sref instanceof QuadCurve2D ||
sref instanceof CubicCurve2D)
{ // REMIND // Some of the source shapes are not proving reliable // enough to do reference verification of the hit // testing results. // Quad/CubicCurve2D have spaghetti test methods that could // very likely contain some bugs. They return a conflicting // answer in maybe 1 out of 20,000 tests. // Area causes a conflicting answer maybe 1 out of // 100 to 1000 runs and it infinite loops maybe 1 // out of 10,000 runs or so. // So, we use some conservative "safe" answers for // these shapes and avoid their hit testing methods.
rContainsSmaller = tContainsRect;
rIntersectsLarger = tIntersectsRect;
rContainsPnt = tContainsPnt;
} else {
rContainsSmaller = sref.contains(srect);
rIntersectsLarger = sref.intersects(lrect);
rContainsPnt = sref.contains(px, py);
}
if (tIntersectsRect) { if (tContainsRect) { if (!tContainsPnt) {
System.out.println("reference shape = "+sref);
System.out.println("pnt = "+pnt);
System.out.println("rect = "+rect);
System.out.println("tbounds = "+stest.getBounds2D()); thrownew RuntimeException("test contains rect, "+ "but not center point");
}
} // Note: (tContainsPnt || tContainsRect) is same as // tContainsPnt because of the test above... if (tContainsPnt) { if (!rIntersectsLarger) {
System.out.println("reference shape = "+sref);
System.out.println("pnt = "+pnt);
System.out.println("rect = "+rect);
System.out.println("lrect = "+lrect);
System.out.println("tbounds = "+stest.getBounds2D());
System.out.println("rbounds = "+sref.getBounds2D()); thrownew RuntimeException("test claims containment, "+ "but no ref intersection");
}
}
} else { if (tContainsRect) { thrownew RuntimeException("test contains rect, "+ "with no intersection");
} if (tContainsPnt) {
System.out.println("reference shape = "+sref);
System.out.println("rect = "+rect); thrownew RuntimeException("test contains point, "+ "with no intersection");
} if (rContainsPnt || rContainsSmaller) {
System.out.println("pnt = "+pnt);
System.out.println("rect = "+rect);
System.out.println("srect = "+lrect); thrownew RuntimeException("test did not intersect, "+ "but ref claims containment");
}
}
}
}
publicvoid test(Creator c) {
testConstructors(c);
testPathConstruction(c);
testAppend(c);
testBounds(c);
testHits(c);
}
publicstaticvoid testAppend(Creator c) { for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i]; if (verbose) System.out.println("append testing "+sref);
PathIterator spi = sref.getPathIterator(null);
Path2D stest = c.makePath(spi.getWindingRule());
stest.append(spi, false);
compare(c, stest, sref, null, 0);
stest.reset();
stest.append(sref, false);
compare(c, stest, sref, null, 0);
stest.reset();
stest.append(sref.getPathIterator(TxComplex), false);
compare(c, stest, sref, TxComplex, 0); // multiple shape append testing... if (sref.getBounds2D().isEmpty()) { // If the first shape is empty, then we really // are not testing multiple appended shapes, // we are just testing appending the AppendShape // to a null path over and over. // Also note that some empty shapes will spit out // a single useless SEG_MOVETO that has no affect // on the outcome, but it makes duplicating the // behavior that Path2D has in that case difficult // when the AppenedShape utility class has to // iterate the exact same segments. So, we will // just ignore all empty shapes here. continue;
}
stest.reset();
stest.append(sref, false);
stest.append(AppendShape, false);
compare(c, stest, new AppendedShape(sref, AppendShape, false), null, 0);
stest.reset();
stest.append(sref, false);
stest.append(AppendShape, true);
compare(c, stest, new AppendedShape(sref, AppendShape, true), null, 0);
stest.reset();
stest.append(sref.getPathIterator(null), false);
stest.append(AppendShape.getPathIterator(null), false);
compare(c, stest, new AppendedShape(sref, AppendShape, false), null, 0);
stest.reset();
stest.append(sref.getPathIterator(null), false);
stest.append(AppendShape.getPathIterator(null), true);
compare(c, stest, new AppendedShape(sref, AppendShape, true), null, 0);
stest.reset();
stest.append(sref.getPathIterator(TxComplex), false);
stest.append(AppendShape.getPathIterator(TxComplex), false);
compare(c, stest, new AppendedShape(sref, AppendShape, false),
TxComplex, 0);
stest.reset();
stest.append(sref.getPathIterator(TxComplex), false);
stest.append(AppendShape.getPathIterator(TxComplex), true);
compare(c, stest, new AppendedShape(sref, AppendShape, true),
TxComplex, 0);
}
}
publicstaticvoid testBounds(Creator c) { for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i]; if (verbose) System.out.println("bounds testing "+sref);
Shape stest = c.makePath(sref);
checkBounds(c.makePath(sref), sref);
testGetBounds2D(stest);
}
testBounds(c, ShortSampleNonZero);
testBounds(c, ShortSampleEvenOdd);
testBounds(c, LongSampleNonZero);
testBounds(c, LongSampleEvenOdd);
}
publicstaticvoid testBounds(Creator c, SampleShape ref) { if (verbose) System.out.println("bounds testing "+ref); if (c.supportsFloatCompose()) {
checkBounds(ref.makeFloatPath(c), ref);
}
checkBounds(ref.makeDoublePath(c), ref);
}
/** * Make sure the {@link Shape#getBounds2D()} returns a Rectangle2D that tightly fits the * shape data. It shouldn't contain lots of dead space (see JDK 8176501), and it shouldn't * leave out any shape path. This test relies on the accuracy of * {@link Shape#intersects(double, double, double, double)}
*/ publicstaticvoid testGetBounds2D(Shape shape) { // first: make sure the shape is actually close to the perimeter of shape.getBounds2D(). // this is the crux of JDK 8176501:
Rectangle2D r = shape.getBounds2D();
if (r.getWidth() == 0 || r.getHeight() == 0) { // this can happen for completely empty paths, which are part of our // edge test cases in this class. return;
}
if (verbose) System.out.println("testGetBounds2D "+shape+", "+r);
Rectangle2D topStrip = new Rectangle2D.Double(r.getMinX(), r.getMinY(), r.getWidth(), yminInterior - r.getMinY());
Rectangle2D leftStrip = new Rectangle2D.Double(r.getMinX(), r.getMinY(), xminInterior - r.getMinX(), r.getHeight());
Rectangle2D bottomStrip = new Rectangle2D.Double(r.getMinX(), ymaxInterior, r.getWidth(), r.getMaxY() - ymaxInterior);
Rectangle2D rightStrip = new Rectangle2D.Double(xmaxInterior, r.getMinY(), r.getMaxX() - xmaxInterior, r.getHeight()); if (!shape.intersects(topStrip)) { if (verbose)
System.out.println("topStrip = "+topStrip); thrownew RuntimeException("the shape must intersect the top strip of its bounds");
} if (!shape.intersects(leftStrip)) { if (verbose)
System.out.println("leftStrip = " + leftStrip); thrownew RuntimeException("the shape must intersect the left strip of its bounds");
} if (!shape.intersects(bottomStrip)) { if (verbose)
System.out.println("bottomStrip = " + bottomStrip); thrownew RuntimeException("the shape must intersect the bottom strip of its bounds");
} if (!shape.intersects(rightStrip)) { if (verbose)
System.out.println("rightStrip = " + rightStrip); thrownew RuntimeException("the shape must intersect the right strip of bounds");
}
// Similarly: make sure our shape doesn't exist OUTSIDE of r, either. To my knowledge this has never // been a problem, but if it did happen this would be an even more serious breach of contract than // the former case.
// k is simply meant to mean "a large number, functionally similar to infinity for this test" double k = 10000.0;
leftStrip = new Rectangle2D.Double(xminExterior - k, -k, k, 3 * k);
rightStrip = new Rectangle2D.Double(xmaxExterior, -k, k, 3 * k);
topStrip = new Rectangle2D.Double(-k, yminExterior - k, 3 * k, k);
bottomStrip = new Rectangle2D.Double(-k, ymaxExterior, 3 * k, k); if (shape.intersects(leftStrip)) { if (verbose)
System.out.println("leftStrip = " + leftStrip); thrownew RuntimeException("the shape must not intersect anything to the left of its bounds");
} if (shape.intersects(rightStrip)) { if (verbose)
System.out.println("rightStrip = " + rightStrip); thrownew RuntimeException("the shape must not intersect anything to the right of its bounds");
} if (shape.intersects(topStrip)) { if (verbose)
System.out.println("topStrip = " + topStrip); thrownew RuntimeException("the shape must not intersect anything above its bounds");
} if (shape.intersects(bottomStrip)) { if (verbose)
System.out.println("bottomStrip = " + bottomStrip); thrownew RuntimeException("the shape must not intersect anything below its bounds");
}
}
publicvoid testHits(Creator c) { for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i]; if (verbose) System.out.println("hit testing "+sref);
Shape stest = c.makePath(sref);
checkHits(c.makePath(sref), sref);
}
testHits(c, ShortSampleNonZero);
testHits(c, ShortSampleEvenOdd); // These take too long to construct the Area for reference testing //testHits(c, LongSampleNonZero); //testHits(c, LongSampleEvenOdd);
}
publicvoid testHits(Creator c, SampleShape ref) { if (verbose) System.out.println("hit testing "+ref); if (c.supportsFloatCompose()) {
checkHits(ref.makeFloatPath(c), ref);
}
checkHits(ref.makeDoublePath(c), ref);
}
publicstaticvoid main(String argv[]) { // as specific failures come up we can add them to this array to make sure they're frequently tested: long[] previousBugSeeds = newlong[] {
// these are all failures related to JDK-8176501
4603421469924484958L,
4596019360892069260L,
4604586530476373958L,
4603766396818608126L
};
verbose = (argv.length > 1);
for (long seed : previousBugSeeds) {
test("", seed);
}
int limit = (argv.length > 0) ? 10000 : 1; for (int i = 0; i < limit; i++) { long seed = Double.doubleToLongBits(Math.random());
test("loop #" + (i + 1), seed);
}
}
UnitTest t = new UnitTest(seed);
t.test(new GPCreator());
t.test(new FltCreator());
t.test(new DblCreator());
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.41Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.