blob: 22c58393f5f8148b5b67806b6640b928e81ef1ba [file] [log] [blame]
/*
* 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.
*/
package org.apache.lucene.spatial.prefix;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.locationtech.spatial4j.shape.Shape;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.StrategyTestCase;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomInt;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
/** Base test harness, ideally for SpatialStrategy impls that have exact results
* (not grid approximated), hence "not fuzzy".
*/
public abstract class RandomSpatialOpStrategyTestCase extends StrategyTestCase {
//Note: this is partially redundant with StrategyTestCase.runTestQuery & testOperation
protected void testOperationRandomShapes(final SpatialOperation operation) throws IOException {
final int numIndexedShapes = randomIntBetween(1, 6);
List<Shape> indexedShapes = new ArrayList<>(numIndexedShapes);
for (int i = 0; i < numIndexedShapes; i++) {
indexedShapes.add(randomIndexedShape());
}
final int numQueryShapes = atLeast(20);
List<Shape> queryShapes = new ArrayList<>(numQueryShapes);
for (int i = 0; i < numQueryShapes; i++) {
queryShapes.add(randomQueryShape());
}
testOperation(operation, indexedShapes, queryShapes, true/*havoc*/);
}
protected void testOperation(final SpatialOperation operation,
List<Shape> indexedShapes, List<Shape> queryShapes, boolean havoc) throws IOException {
//first show that when there's no data, a query will result in no results
{
Query query = strategy.makeQuery(new SpatialArgs(operation, randomQueryShape()));
SearchResults searchResults = executeQuery(query, 1);
assertEquals(0, searchResults.numFound);
}
//Main index loop:
for (int i = 0; i < indexedShapes.size(); i++) {
Shape shape = indexedShapes.get(i);
adoc(""+i, shape);
if (havoc && random().nextInt(10) == 0)
commit();//intermediate commit, produces extra segments
}
if (havoc) {
//delete some documents randomly
for (int id = 0; id < indexedShapes.size(); id++) {
if (random().nextInt(10) == 0) {
deleteDoc(""+id);
indexedShapes.set(id, null);
}
}
}
commit();
//Main query loop:
for (int queryIdx = 0; queryIdx < queryShapes.size(); queryIdx++) {
final Shape queryShape = queryShapes.get(queryIdx);
if (havoc)
preQueryHavoc();
//Generate truth via brute force:
// We ensure true-positive matches (if the predicate on the raw shapes match
// then the search should find those same matches).
Set<String> expectedIds = new LinkedHashSet<>();//true-positives
for (int id = 0; id < indexedShapes.size(); id++) {
Shape indexedShape = indexedShapes.get(id);
if (indexedShape == null)
continue;
if (operation.evaluate(indexedShape, queryShape)) {
expectedIds.add(""+id);
}
}
//Search and verify results
SpatialArgs args = new SpatialArgs(operation, queryShape);
Query query = strategy.makeQuery(args);
SearchResults got = executeQuery(query, 100);
Set<String> remainingExpectedIds = new LinkedHashSet<>(expectedIds);
for (SearchResult result : got.results) {
String id = result.getId();
if (!remainingExpectedIds.remove(id)) {
fail("qIdx:" + queryIdx + " Shouldn't match", id, indexedShapes, queryShape, operation);
}
}
if (!remainingExpectedIds.isEmpty()) {
String id = remainingExpectedIds.iterator().next();
fail("qIdx:" + queryIdx + " Should have matched", id, indexedShapes, queryShape, operation);
}
}
}
private void fail(String label, String id, List<Shape> indexedShapes, Shape queryShape, SpatialOperation operation) {
fail("[" + operation + "] " + label
+ " I#" + id + ":" + indexedShapes.get(Integer.parseInt(id)) + " Q:" + queryShape);
}
protected void preQueryHavoc() {
if (strategy instanceof RecursivePrefixTreeStrategy) {
RecursivePrefixTreeStrategy rpts = (RecursivePrefixTreeStrategy) strategy;
int scanLevel = randomInt(rpts.getGrid().getMaxLevels());
rpts.setPrefixGridScanLevel(scanLevel);
}
}
protected abstract Shape randomIndexedShape();
protected abstract Shape randomQueryShape();
}