001: package org.apache.lucene.search;
002:
003: /**
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import java.util.ConcurrentModificationException;
021:
022: import junit.framework.TestCase;
023:
024: import org.apache.lucene.analysis.WhitespaceAnalyzer;
025: import org.apache.lucene.document.Document;
026: import org.apache.lucene.document.Field;
027: import org.apache.lucene.index.IndexReader;
028: import org.apache.lucene.index.IndexWriter;
029: import org.apache.lucene.index.Term;
030: import org.apache.lucene.search.Hits;
031: import org.apache.lucene.search.IndexSearcher;
032: import org.apache.lucene.search.Query;
033: import org.apache.lucene.store.Directory;
034: import org.apache.lucene.store.RAMDirectory;
035:
036: /**
037: * Test Hits searches with interleaved deletions.
038: *
039: * See {@link http://issues.apache.org/jira/browse/LUCENE-1096}.
040: */
041: public class TestSearchHitsWithDeletions extends TestCase {
042:
043: private static boolean VERBOSE = false;
044: private static final String TEXT_FIELD = "text";
045: private static final int N = 16100;
046:
047: private static Directory directory;
048:
049: public void setUp() throws Exception {
050: // Create an index writer.
051: directory = new RAMDirectory();
052: IndexWriter writer = new IndexWriter(directory,
053: new WhitespaceAnalyzer(), true);
054: for (int i = 0; i < N; i++) {
055: writer.addDocument(createDocument(i));
056: }
057: writer.optimize();
058: writer.close();
059: }
060:
061: /**
062: * Deletions during search should not alter previously retrieved hits.
063: */
064: public void testSearchHitsDeleteAll() throws Exception {
065: doTestSearchHitsDeleteEvery(1, false);
066: }
067:
068: /**
069: * Deletions during search should not alter previously retrieved hits.
070: */
071: public void testSearchHitsDeleteEvery2ndHit() throws Exception {
072: doTestSearchHitsDeleteEvery(2, false);
073: }
074:
075: /**
076: * Deletions during search should not alter previously retrieved hits.
077: */
078: public void testSearchHitsDeleteEvery4thHit() throws Exception {
079: doTestSearchHitsDeleteEvery(4, false);
080: }
081:
082: /**
083: * Deletions during search should not alter previously retrieved hits.
084: */
085: public void testSearchHitsDeleteEvery8thHit() throws Exception {
086: doTestSearchHitsDeleteEvery(8, false);
087: }
088:
089: /**
090: * Deletions during search should not alter previously retrieved hits.
091: */
092: public void testSearchHitsDeleteEvery90thHit() throws Exception {
093: doTestSearchHitsDeleteEvery(90, false);
094: }
095:
096: /**
097: * Deletions during search should not alter previously retrieved hits,
098: * and deletions that affect total number of hits should throw the
099: * correct exception when trying to fetch "too many".
100: */
101: public void testSearchHitsDeleteEvery8thHitAndInAdvance()
102: throws Exception {
103: doTestSearchHitsDeleteEvery(8, true);
104: }
105:
106: /**
107: * Verify that ok also with no deletions at all.
108: */
109: public void testSearchHitsNoDeletes() throws Exception {
110: doTestSearchHitsDeleteEvery(N + 100, false);
111: }
112:
113: /**
114: * Deletions that affect total number of hits should throw the
115: * correct exception when trying to fetch "too many".
116: */
117: public void testSearchHitsDeleteInAdvance() throws Exception {
118: doTestSearchHitsDeleteEvery(N + 100, true);
119: }
120:
121: /**
122: * Intermittent deletions during search, should not alter previously retrieved hits.
123: * (Using a debugger to verify that the check in Hits is performed only
124: */
125: public void testSearchHitsDeleteIntermittent() throws Exception {
126: doTestSearchHitsDeleteEvery(-1, false);
127: }
128:
129: private void doTestSearchHitsDeleteEvery(int k,
130: boolean deleteInFront) throws Exception {
131: boolean intermittent = k < 0;
132: log("Test search hits with "
133: + (intermittent ? "intermittent deletions."
134: : "deletions of every " + k + " hit."));
135: IndexSearcher searcher = new IndexSearcher(directory);
136: IndexReader reader = searcher.getIndexReader();
137: Query q = new TermQuery(new Term(TEXT_FIELD, "text")); // matching all docs
138: Hits hits = searcher.search(q);
139: log("Got " + hits.length() + " results");
140: assertEquals("must match all " + N + " docs, not only "
141: + hits.length() + " docs!", N, hits.length());
142: if (deleteInFront) {
143: log("deleting hits that was not yet retrieved!");
144: reader.deleteDocument(reader.maxDoc() - 1);
145: reader.deleteDocument(reader.maxDoc() - 2);
146: reader.deleteDocument(reader.maxDoc() - 3);
147: }
148: try {
149: for (int i = 0; i < hits.length(); i++) {
150: int id = hits.id(i);
151: assertEquals("Hit " + i + " has doc id " + hits.id(i)
152: + " instead of " + i, i, hits.id(i));
153: if ((intermittent && (i == 50 || i == 250 || i == 950))
154: || //100-yes, 200-no, 400-yes, 800-no, 1600-yes
155: (!intermittent && (k < 2 || (i > 0 && i % k == 0)))) {
156: Document doc = hits.doc(id);
157: log("Deleting hit " + i + " - doc " + doc
158: + " with id " + id);
159: reader.deleteDocument(id);
160: }
161: if (intermittent) {
162: // check internal behavior of Hits (go 50 ahead of getMoreDocs points because the deletions cause to use more of the available hits)
163: if (i == 150 || i == 450 || i == 1650) {
164: assertTrue(
165: "Hit "
166: + i
167: + ": hits should have checked for deletions in last call to getMoreDocs()",
168: hits.debugCheckedForDeletions);
169: } else if (i == 50 || i == 250 || i == 850) {
170: assertFalse(
171: "Hit "
172: + i
173: + ": hits should have NOT checked for deletions in last call to getMoreDocs()",
174: hits.debugCheckedForDeletions);
175: }
176: }
177: }
178: } catch (ConcurrentModificationException e) {
179: // this is the only valid exception, and only when deletng in front.
180: assertTrue(
181: e.getMessage()
182: + " not expected unless deleting hits that were not yet seen!",
183: deleteInFront);
184: }
185: searcher.close();
186: }
187:
188: private static Document createDocument(int id) {
189: Document doc = new Document();
190: doc.add(new Field(TEXT_FIELD, "text of document" + id,
191: Field.Store.YES, Field.Index.TOKENIZED));
192: return doc;
193: }
194:
195: private static void log(String s) {
196: if (VERBOSE) {
197: System.out.println(s);
198: }
199: }
200: }
|