| /* |
| * 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(); |
| } |