You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2015/04/20 15:40:54 UTC

svn commit: r1674845 - in /lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j: Geo3dShapeTest.java RandomizedShapeTest.java RectIntersectionTestHelper.java TestLog.java

Author: dsmiley
Date: Mon Apr 20 13:40:54 2015
New Revision: 1674845

URL: http://svn.apache.org/r1674845
Log:
Port RectIntersectionTestHelper (and TestLog and RandomizedShapeTest) from Spatial4j.  Add Geo3dShapeTest, initially added GeoCircle; others are TBD. Test fails.

Added:
    lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeTest.java   (with props)
    lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RandomizedShapeTest.java   (with props)
    lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java   (with props)
    lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/TestLog.java   (with props)

Added: lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeTest.java?rev=1674845&view=auto
==============================================================================
--- lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeTest.java (added)
+++ lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeTest.java Mon Apr 20 13:40:54 2015
@@ -0,0 +1,87 @@
+package org.apache.lucene.spatial.spatial4j;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Random;
+
+import com.carrotsearch.randomizedtesting.RandomizedContext;
+import com.carrotsearch.randomizedtesting.annotations.Seed;
+import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.distance.DistanceUtils;
+import com.spatial4j.core.shape.Point;
+import org.apache.lucene.spatial.spatial4j.geo3d.GeoCircle;
+import org.apache.lucene.spatial.spatial4j.geo3d.GeoPoint;
+import org.apache.lucene.spatial.spatial4j.geo3d.GeoShape;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static com.spatial4j.core.distance.DistanceUtils.DEGREES_TO_RADIANS;
+
+public class Geo3dShapeTest extends RandomizedShapeTest {
+  @Rule
+  public final TestLog testLog = TestLog.instance;
+
+  static Random random() {
+    return RandomizedContext.current().getRandom();
+  }
+
+  {
+    ctx = SpatialContext.GEO;
+  }
+
+  @Test
+  @Seed("FAD1BAB12B6DCCFE")
+  public void testGeoCircleRect() {
+    new RectIntersectionTestHelper<Geo3dShape>(ctx) {
+
+      @Override
+      protected Geo3dShape generateRandomShape(Point nearP) {
+        // Circles
+        while (true) {
+          final int circleRadius = random().nextInt(180);
+          final Point point = nearP;
+          try {
+            final GeoShape shape = new GeoCircle(point.getY() * DEGREES_TO_RADIANS, point.getX() * DEGREES_TO_RADIANS,
+                circleRadius * DEGREES_TO_RADIANS);
+            return new Geo3dShape(shape, ctx);
+          } catch (IllegalArgumentException e) {
+            // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
+            // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.
+            continue;
+          }
+        }
+      }
+
+      @Override
+      protected Point randomPointInEmptyShape(Geo3dShape shape) {
+        GeoPoint geoPoint = ((GeoCircle)shape.shape).center;
+        return geoPointToSpatial4jPoint(geoPoint);
+      }
+
+    }.testRelateWithRectangle();
+  }
+
+  //TODO PORT OTHER TESTS
+
+
+  private Point geoPointToSpatial4jPoint(GeoPoint geoPoint) {
+    return ctx.makePoint(geoPoint.x * DistanceUtils.RADIANS_TO_DEGREES,
+        geoPoint.y * DistanceUtils.RADIANS_TO_DEGREES);
+  }
+
+}

Added: lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RandomizedShapeTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RandomizedShapeTest.java?rev=1674845&view=auto
==============================================================================
--- lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RandomizedShapeTest.java (added)
+++ lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RandomizedShapeTest.java Mon Apr 20 13:40:54 2015
@@ -0,0 +1,285 @@
+package org.apache.lucene.spatial.spatial4j;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import com.carrotsearch.randomizedtesting.RandomizedTest;
+import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.distance.DistanceUtils;
+import com.spatial4j.core.shape.Circle;
+import com.spatial4j.core.shape.Point;
+import com.spatial4j.core.shape.Rectangle;
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.SpatialRelation;
+import com.spatial4j.core.shape.impl.Range;
+
+import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
+import static com.spatial4j.core.shape.SpatialRelation.WITHIN;
+
+/**
+ * A base test class with utility methods to help test shapes.
+ * Extends from RandomizedTest.
+ */
+public abstract class RandomizedShapeTest extends RandomizedTest {
+
+  protected static final double EPS = 10e-9;
+
+  protected SpatialContext ctx;//needs to be set ASAP
+
+  /** Used to reduce the space of numbers to increase the likelihood that
+   * random numbers become equivalent, and thus trigger different code paths.
+   * Also makes some random shapes easier to manually examine.
+   */
+  protected final double DIVISIBLE = 2;// even coordinates; (not always used)
+
+  protected RandomizedShapeTest() {
+  }
+
+  public RandomizedShapeTest(SpatialContext ctx) {
+    this.ctx = ctx;
+  }
+
+  public static void checkShapesImplementEquals( Class[] classes ) {
+    for( Class clazz : classes ) {
+      try {
+        clazz.getDeclaredMethod( "equals", Object.class );
+      } catch (Exception e) {
+        fail("Shape needs to define 'equals' : " + clazz.getName());
+      }
+      try {
+        clazz.getDeclaredMethod( "hashCode" );
+      } catch (Exception e) {
+        fail("Shape needs to define 'hashCode' : " + clazz.getName());
+      }
+    }
+  }
+
+  //These few norm methods normalize the arguments for creating a shape to
+  // account for the dateline. Some tests loop past the dateline or have offsets
+  // that go past it and it's easier to have them coded that way and correct for
+  // it here.  These norm methods should be used when needed, not frivolously.
+
+  protected double normX(double x) {
+    return ctx.isGeo() ? DistanceUtils.normLonDEG(x) : x;
+  }
+
+  protected double normY(double y) {
+    return ctx.isGeo() ? DistanceUtils.normLatDEG(y) : y;
+  }
+
+  protected Rectangle makeNormRect(double minX, double maxX, double minY, double maxY) {
+    if (ctx.isGeo()) {
+      if (Math.abs(maxX - minX) >= 360) {
+        minX = -180;
+        maxX = 180;
+      } else {
+        minX = DistanceUtils.normLonDEG(minX);
+        maxX = DistanceUtils.normLonDEG(maxX);
+      }
+
+    } else {
+      if (maxX < minX) {
+        double t = minX;
+        minX = maxX;
+        maxX = t;
+      }
+      minX = boundX(minX, ctx.getWorldBounds());
+      maxX = boundX(maxX, ctx.getWorldBounds());
+    }
+    if (maxY < minY) {
+      double t = minY;
+      minY = maxY;
+      maxY = t;
+    }
+    minY = boundY(minY, ctx.getWorldBounds());
+    maxY = boundY(maxY, ctx.getWorldBounds());
+    return ctx.makeRectangle(minX, maxX, minY, maxY);
+  }
+
+  public static double divisible(double v, double divisible) {
+    return (int) (Math.round(v / divisible) * divisible);
+  }
+
+  protected double divisible(double v) {
+    return divisible(v, DIVISIBLE);
+  }
+
+  /** reset()'s p, and confines to world bounds. Might not be divisible if
+   * the world bound isn't divisible too.
+   */
+  protected Point divisible(Point p) {
+    Rectangle bounds = ctx.getWorldBounds();
+    double newX = boundX( divisible(p.getX()), bounds );
+    double newY = boundY( divisible(p.getY()), bounds );
+    p.reset(newX, newY);
+    return p;
+  }
+
+  static double boundX(double i, Rectangle bounds) {
+    return bound(i, bounds.getMinX(), bounds.getMaxX());
+  }
+
+  static double boundY(double i, Rectangle bounds) {
+    return bound(i, bounds.getMinY(), bounds.getMaxY());
+  }
+
+  static double bound(double i, double min, double max) {
+    if (i < min) return min;
+    if (i > max) return max;
+    return i;
+  }
+
+  protected void assertRelation(SpatialRelation expected, Shape a, Shape b) {
+    assertRelation(null, expected, a, b);
+  }
+
+  protected void assertRelation(String msg, SpatialRelation expected, Shape a, Shape b) {
+    _assertIntersect(msg, expected, a, b);
+    //check flipped a & b w/ transpose(), while we're at it
+    _assertIntersect(msg, expected.transpose(), b, a);
+  }
+
+  private void _assertIntersect(String msg, SpatialRelation expected, Shape a, Shape b) {
+    SpatialRelation sect = a.relate(b);
+    if (sect == expected)
+      return;
+    msg = ((msg == null) ? "" : msg+"\r") + a +" intersect "+b;
+    if (expected == WITHIN || expected == CONTAINS) {
+      if (a.getClass().equals(b.getClass())) // they are the same shape type
+        assertEquals(msg,a,b);
+      else {
+        //they are effectively points or lines that are the same location
+        assertTrue(msg,!a.hasArea());
+        assertTrue(msg,!b.hasArea());
+
+        Rectangle aBBox = a.getBoundingBox();
+        Rectangle bBBox = b.getBoundingBox();
+        if (aBBox.getHeight() == 0 && bBBox.getHeight() == 0
+            && (aBBox.getMaxY() == 90 && bBBox.getMaxY() == 90
+            || aBBox.getMinY() == -90 && bBBox.getMinY() == -90))
+          ;//== a point at the pole
+        else
+          assertEquals(msg, aBBox, bBBox);
+      }
+    } else {
+      assertEquals(msg,expected,sect);//always fails
+    }
+  }
+
+  protected void assertEqualsRatio(String msg, double expected, double actual) {
+    double delta = Math.abs(actual - expected);
+    double base = Math.min(actual, expected);
+    double deltaRatio = base==0 ? delta : Math.min(delta,delta / base);
+    assertEquals(msg,0,deltaRatio, EPS);
+  }
+
+  protected int randomIntBetweenDivisible(int start, int end) {
+    return randomIntBetweenDivisible(start, end, (int)DIVISIBLE);
+  }
+  /** Returns a random integer between [start, end]. Integers between must be divisible by the 3rd argument. */
+  protected int randomIntBetweenDivisible(int start, int end, int divisible) {
+    // DWS: I tested this
+    int divisStart = (int) Math.ceil( (start+1) / (double)divisible );
+    int divisEnd = (int) Math.floor( (end-1) / (double)divisible );
+    int divisRange = Math.max(0,divisEnd - divisStart + 1);
+    int r = randomInt(1 + divisRange);//remember that '0' is counted
+    if (r == 0)
+      return start;
+    if (r == 1)
+      return end;
+    return (r-2 + divisStart)*divisible;
+  }
+
+  protected Rectangle randomRectangle(Point nearP) {
+    Rectangle bounds = ctx.getWorldBounds();
+    if (nearP == null)
+      nearP = randomPointIn(bounds);
+
+    Range xRange = randomRange(rarely() ? 0 : nearP.getX(), Range.xRange(bounds, ctx));
+    Range yRange = randomRange(rarely() ? 0 : nearP.getY(), Range.yRange(bounds, ctx));
+
+    return makeNormRect(
+        divisible(xRange.getMin()),
+        divisible(xRange.getMax()),
+        divisible(yRange.getMin()),
+        divisible(yRange.getMax()) );
+  }
+
+  private Range randomRange(double near, Range bounds) {
+    double mid = near + randomGaussian() * bounds.getWidth() / 6;
+    double width = Math.abs(randomGaussian()) * bounds.getWidth() / 6;//1/3rd
+    return new Range(mid - width / 2, mid + width / 2);
+  }
+
+  private double randomGaussianZeroTo(double max) {
+    if (max == 0)
+      return max;
+    assert max > 0;
+    double r;
+    do {
+      r = Math.abs(randomGaussian()) * (max * 0.50);
+    } while (r > max);
+    return r;
+  }
+
+  protected Rectangle randomRectangle(int divisible) {
+    double rX = randomIntBetweenDivisible(-180, 180, divisible);
+    double rW = randomIntBetweenDivisible(0, 360, divisible);
+    double rY1 = randomIntBetweenDivisible(-90, 90, divisible);
+    double rY2 = randomIntBetweenDivisible(-90, 90, divisible);
+    double rYmin = Math.min(rY1,rY2);
+    double rYmax = Math.max(rY1,rY2);
+    if (rW > 0 && rX == 180)
+      rX = -180;
+    return makeNormRect(rX, rX + rW, rYmin, rYmax);
+  }
+
+  protected Point randomPoint() {
+    return randomPointIn(ctx.getWorldBounds());
+  }
+
+  protected Point randomPointIn(Circle c) {
+    double d = c.getRadius() * randomDouble();
+    double angleDEG = 360 * randomDouble();
+    Point p = ctx.getDistCalc().pointOnBearing(c.getCenter(), d, angleDEG, ctx, null);
+    assertEquals(CONTAINS,c.relate(p));
+    return p;
+  }
+
+  protected Point randomPointIn(Rectangle r) {
+    double x = r.getMinX() + randomDouble()*r.getWidth();
+    double y = r.getMinY() + randomDouble()*r.getHeight();
+    x = normX(x);
+    y = normY(y);
+    Point p = ctx.makePoint(x,y);
+    assertEquals(CONTAINS,r.relate(p));
+    return p;
+  }
+
+  protected Point randomPointIn(Shape shape) {
+    if (!shape.hasArea())// or try the center?
+      throw new UnsupportedOperationException("Need area to define shape!");
+    Rectangle bbox = shape.getBoundingBox();
+    Point p;
+    do {
+      p = randomPointIn(bbox);
+    } while (!bbox.relate(p).intersects());
+    return p;
+  }
+}
+

Added: lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java?rev=1674845&view=auto
==============================================================================
--- lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java (added)
+++ lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/RectIntersectionTestHelper.java Mon Apr 20 13:40:54 2015
@@ -0,0 +1,180 @@
+package org.apache.lucene.spatial.spatial4j;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.spatial4j.core.context.SpatialContext;
+import com.spatial4j.core.shape.Point;
+import com.spatial4j.core.shape.Rectangle;
+import com.spatial4j.core.shape.Shape;
+import com.spatial4j.core.shape.SpatialRelation;
+import com.spatial4j.core.shape.impl.InfBufLine;
+import com.spatial4j.core.shape.impl.PointImpl;
+
+import static com.spatial4j.core.shape.SpatialRelation.CONTAINS;
+import static com.spatial4j.core.shape.SpatialRelation.DISJOINT;
+
+public abstract class RectIntersectionTestHelper<S extends Shape> extends RandomizedShapeTest {
+
+  public RectIntersectionTestHelper(SpatialContext ctx) {
+    super(ctx);
+  }
+
+  protected abstract S generateRandomShape(Point nearP);
+
+  protected abstract Point randomPointInEmptyShape(S shape);
+
+  @SuppressWarnings("unchecked")
+  @Override
+  protected Point randomPointIn(Shape shape) {
+    if (!shape.hasArea())
+      return randomPointInEmptyShape((S) shape);
+    return super.randomPointIn(shape);
+  }
+
+  public void testRelateWithRectangle() {
+    //counters for the different intersection cases
+    int i_C = 0, i_I = 0, i_W = 0, i_D = 0, i_bboxD = 0;
+    int laps = 0;
+    final int MINLAPSPERCASE = scaledRandomIntBetween(20, 200);
+    while(i_C < MINLAPSPERCASE || i_I < MINLAPSPERCASE || i_W < MINLAPSPERCASE
+        || i_D < MINLAPSPERCASE || i_bboxD < MINLAPSPERCASE) {
+      laps++;
+
+      TestLog.clear();
+
+      Point nearP = randomPointIn(ctx.getWorldBounds());
+
+      S s = generateRandomShape(nearP);
+
+      Rectangle r = randomRectangle(s.getBoundingBox().getCenter());
+
+      SpatialRelation ic = s.relate(r);
+
+      TestLog.log("S-R Rel: {}, Shape {}, Rectangle {}", ic, s, r);
+
+      try {
+        int MAX_TRIES = scaledRandomIntBetween(10, 100);
+        switch (ic) {
+          case CONTAINS:
+            i_C++;
+            for (int j = 0; j < MAX_TRIES; j++) {
+              Point p = randomPointIn(r);
+              assertRelation(null, CONTAINS, s, p);
+            }
+            break;
+
+          case WITHIN:
+            i_W++;
+            for (int j = 0; j < MAX_TRIES; j++) {
+              Point p = randomPointIn(s);
+              assertRelation(null, CONTAINS, r, p);
+            }
+            break;
+
+          case DISJOINT:
+            if (!s.getBoundingBox().relate(r).intersects()) {//bboxes are disjoint
+              i_bboxD++;
+              if (i_bboxD > MINLAPSPERCASE)
+                break;
+            } else {
+              i_D++;
+            }
+            for (int j = 0; j < MAX_TRIES; j++) {
+              Point p = randomPointIn(r);
+              assertRelation(null, DISJOINT, s, p);
+            }
+            break;
+
+          case INTERSECTS:
+            i_I++;
+            SpatialRelation pointR = null;//set once
+            Rectangle randomPointSpace = null;
+            MAX_TRIES = 1000;//give many attempts
+            for (int j = 0; j < MAX_TRIES; j++) {
+              Point p;
+              if (j < 4) {
+                p = new PointImpl(0, 0, ctx);
+                InfBufLine.cornerByQuadrant(r, j + 1, p);
+              } else {
+                if (randomPointSpace == null) {
+                  if (pointR == DISJOINT) {
+                    randomPointSpace = intersectRects(r,s.getBoundingBox());
+                  } else {//CONTAINS
+                    randomPointSpace = r;
+                  }
+                }
+                p = randomPointIn(randomPointSpace);
+              }
+              SpatialRelation pointRNew = s.relate(p);
+              if (pointR == null) {
+                pointR = pointRNew;
+              } else if (pointR != pointRNew) {
+                break;
+              } else if (j >= MAX_TRIES) {
+                //TODO consider logging instead of failing
+                fail("Tried intersection brute-force too many times without success");
+              }
+            }
+
+            break;
+
+          default: fail(""+ic);
+        }
+      } catch (AssertionError e) {
+        onAssertFail(e, s, r, ic);
+      }
+      if (laps > MINLAPSPERCASE * 1000)
+        fail("Did not find enough intersection cases in a reasonable number" +
+            " of random attempts. CWIDbD: "+i_C+","+i_W+","+i_I+","+i_D+","+i_bboxD
+            + "  Laps exceeded "+MINLAPSPERCASE * 1000);
+    }
+    System.out.println("Laps: "+laps + " CWIDbD: "+i_C+","+i_W+","+i_I+","+i_D+","+i_bboxD);
+  }
+
+  protected void onAssertFail(AssertionError e, S s, Rectangle r, SpatialRelation ic) {
+    throw e;
+  }
+
+  private Rectangle intersectRects(Rectangle r1, Rectangle r2) {
+    assert r1.relate(r2).intersects();
+    final double minX, maxX;
+    if (r1.relateXRange(r2.getMinX(),r2.getMinX()).intersects()) {
+      minX = r2.getMinX();
+    } else {
+      minX = r1.getMinX();
+    }
+    if (r1.relateXRange(r2.getMaxX(),r2.getMaxX()).intersects()) {
+      maxX = r2.getMaxX();
+    } else {
+      maxX = r1.getMaxX();
+    }
+    final double minY, maxY;
+    if (r1.relateYRange(r2.getMinY(),r2.getMinY()).intersects()) {
+      minY = r2.getMinY();
+    } else {
+      minY = r1.getMinY();
+    }
+    if (r1.relateYRange(r2.getMaxY(),r2.getMaxY()).intersects()) {
+      maxY = r2.getMaxY();
+    } else {
+      maxY = r1.getMaxY();
+    }
+    return ctx.makeRectangle(minX, maxX, minY, maxY);
+  }
+
+}
\ No newline at end of file

Added: lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/TestLog.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/TestLog.java?rev=1674845&view=auto
==============================================================================
--- lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/TestLog.java (added)
+++ lucene/dev/branches/lucene6196/lucene/spatial/src/test/org/apache/lucene/spatial/spatial4j/TestLog.java Mon Apr 20 13:40:54 2015
@@ -0,0 +1,83 @@
+package org.apache.lucene.spatial.spatial4j;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
+
+/**
+ * A utility logger for tests in which log statements are logged following
+ * test failure only.  Add this to a JUnit based test class with a {@link org.junit.Rule}
+ * annotation.
+ */
+public class TestLog extends TestRuleAdapter {
+
+  //TODO does this need to be threadsafe (such as via thread-local state)?
+  private static ArrayList<LogEntry> logStack = new ArrayList<LogEntry>();
+  private static final int MAX_LOGS = 1000;
+
+  public static final TestLog instance = new TestLog();
+
+  private TestLog() {}
+
+  @Override
+  protected void before() throws Throwable {
+    logStack.clear();
+  }
+
+  @Override
+  protected void afterAlways(List<Throwable> errors) throws Throwable {
+    if (!errors.isEmpty())
+      logThenClear();
+  }
+
+  private void logThenClear() {
+    for (LogEntry entry : logStack) {
+      //no SLF4J in Lucene... fallback to this
+      if (entry.args != null && entry.args.length > 0) {
+        System.out.println(entry.msg + " " + Arrays.asList(entry.args) + "(no slf4j subst; sorry)");
+      } else {
+        System.out.println(entry.msg);
+      }
+    }
+    logStack.clear();
+  }
+
+  public static void clear() {
+    logStack.clear();
+  }
+
+  /**
+   * Enqueues a log message with substitution arguments ala SLF4J (i.e. {} syntax).
+   * If the test fails then it'll be logged then, otherwise it'll be forgotten.
+   */
+  public static void log(String msg, Object... args) {
+    if (logStack.size() > MAX_LOGS) {
+      throw new RuntimeException("Too many log statements: "+logStack.size() + " > "+MAX_LOGS);
+    }
+    LogEntry entry = new LogEntry();
+    entry.msg = msg;
+    entry.args = args;
+    logStack.add(entry);
+  }
+
+  private static class LogEntry { String msg; Object[] args; }
+}