pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / test / org / apache / lucene / search / TestTimeLimitingCollector.java
1 package org.apache.lucene.search;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.IOException;
21 import java.util.BitSet;
22
23 import org.apache.lucene.analysis.MockAnalyzer;
24 import org.apache.lucene.document.Document;
25 import org.apache.lucene.document.Field;
26 import org.apache.lucene.index.IndexReader;
27 import org.apache.lucene.index.RandomIndexWriter;
28 import org.apache.lucene.queryParser.QueryParser;
29 import org.apache.lucene.search.TimeLimitingCollector.TimeExceededException;
30 import org.apache.lucene.search.TimeLimitingCollector.TimerThread;
31 import org.apache.lucene.store.Directory;
32 import org.apache.lucene.util.Counter;
33 import org.apache.lucene.util.LuceneTestCase;
34 import org.apache.lucene.util.ThreadInterruptedException;
35
36 /**
37  * Tests the {@link TimeLimitingCollector}.  This test checks (1) search
38  * correctness (regardless of timeout), (2) expected timeout behavior,
39  * and (3) a sanity test with multiple searching threads.
40  */
41 public class TestTimeLimitingCollector extends LuceneTestCase {
42   private static final int SLOW_DOWN = 3;
43   private static final long TIME_ALLOWED = 17 * SLOW_DOWN; // so searches can find about 17 docs.
44   
45   // max time allowed is relaxed for multithreading tests. 
46   // the multithread case fails when setting this to 1 (no slack) and launching many threads (>2000).  
47   // but this is not a real failure, just noise.
48   private static final double MULTI_THREAD_SLACK = 7;      
49             
50   private static final int N_DOCS = 3000;
51   private static final int N_THREADS = 50;
52
53   private Searcher searcher;
54   private Directory directory;
55   private IndexReader reader;
56
57   private final String FIELD_NAME = "body";
58   private Query query;
59   private Counter counter;
60   private TimerThread counterThread;
61
62   /**
63    * initializes searcher with a document set
64    */
65   @Override
66   public void setUp() throws Exception {
67     super.setUp();
68     counter = Counter.newCounter(true);
69     counterThread = new TimerThread(counter);
70     counterThread.start();
71     final String docText[] = {
72         "docThatNeverMatchesSoWeCanRequireLastDocCollectedToBeGreaterThanZero",
73         "one blah three",
74         "one foo three multiOne",
75         "one foobar three multiThree",
76         "blueberry pancakes",
77         "blueberry pie",
78         "blueberry strudel",
79         "blueberry pizza",
80     };
81     directory = newDirectory();
82     RandomIndexWriter iw = new RandomIndexWriter(random, directory, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMergePolicy(newLogMergePolicy()));
83     
84     for (int i=0; i<N_DOCS; i++) {
85       add(docText[i%docText.length], iw);
86     }
87     reader = iw.getReader();
88     iw.close();
89     searcher = newSearcher(reader);
90
91     String qtxt = "one";
92     // start from 1, so that the 0th doc never matches
93     for (int i = 1; i < docText.length; i++) {
94       qtxt += ' ' + docText[i]; // large query so that search will be longer
95     }
96     QueryParser queryParser = new QueryParser(TEST_VERSION_CURRENT, FIELD_NAME, new MockAnalyzer(random));
97     query = queryParser.parse(qtxt);
98     
99     // warm the searcher
100     searcher.search(query, null, 1000);
101   }
102
103   @Override
104   public void tearDown() throws Exception {
105     searcher.close();
106     reader.close();
107     directory.close();
108     counterThread.stopTimer();
109     counterThread.join();
110     super.tearDown();
111   }
112
113   private void add(String value, RandomIndexWriter iw) throws IOException {
114     Document d = new Document();
115     d.add(newField(FIELD_NAME, value, Field.Store.NO, Field.Index.ANALYZED));
116     iw.addDocument(d);
117   }
118
119   private void search(Collector collector) throws Exception {
120     searcher.search(query, collector);
121   }
122
123   /**
124    * test search correctness with no timeout
125    */
126   public void testSearch() {
127     doTestSearch();
128   }
129   
130   private void doTestSearch() {
131     int totalResults = 0;
132     int totalTLCResults = 0;
133     try {
134       MyHitCollector myHc = new MyHitCollector();
135       search(myHc);
136       totalResults = myHc.hitCount();
137       
138       myHc = new MyHitCollector();
139       long oneHour = 3600000;
140       Collector tlCollector = createTimedCollector(myHc, oneHour, false);
141       search(tlCollector);
142       totalTLCResults = myHc.hitCount();
143     } catch (Exception e) {
144       e.printStackTrace();
145       assertTrue("Unexpected exception: "+e, false); //==fail
146     }
147     assertEquals( "Wrong number of results!", totalResults, totalTLCResults );
148   }
149
150   private Collector createTimedCollector(MyHitCollector hc, long timeAllowed, boolean greedy) {
151     TimeLimitingCollector res = new TimeLimitingCollector(hc, counter, timeAllowed);
152     res.setGreedy(greedy); // set to true to make sure at least one doc is collected.
153     return res;
154   }
155
156   /**
157    * Test that timeout is obtained, and soon enough!
158    */
159   public void testTimeoutGreedy() {
160     doTestTimeout(false, true);
161   }
162   
163   /**
164    * Test that timeout is obtained, and soon enough!
165    */
166   public void testTimeoutNotGreedy() {
167     doTestTimeout(false, false);
168   }
169
170   private void doTestTimeout(boolean multiThreaded, boolean greedy) {
171     // setup
172     MyHitCollector myHc = new MyHitCollector();
173     myHc.setSlowDown(SLOW_DOWN);
174     Collector tlCollector = createTimedCollector(myHc, TIME_ALLOWED, greedy);
175
176     // search
177     TimeExceededException timoutException = null;
178     try {
179       search(tlCollector);
180     } catch (TimeExceededException x) {
181       timoutException = x;
182     } catch (Exception e) {
183       assertTrue("Unexpected exception: "+e, false); //==fail
184     }
185     
186     // must get exception
187     assertNotNull( "Timeout expected!", timoutException );
188
189     // greediness affect last doc collected
190     int exceptionDoc = timoutException.getLastDocCollected();
191     int lastCollected = myHc.getLastDocCollected(); 
192     assertTrue( "doc collected at timeout must be > 0!", exceptionDoc > 0 );
193     if (greedy) {
194       assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" != lastCollected="+lastCollected, exceptionDoc==lastCollected);
195       assertTrue("greedy, but no hits found!", myHc.hitCount() > 0 );
196     } else {
197       assertTrue("greedy="+greedy+" exceptionDoc="+exceptionDoc+" not > lastCollected="+lastCollected, exceptionDoc>lastCollected);
198     }
199
200     // verify that elapsed time at exception is within valid limits
201     assertEquals( timoutException.getTimeAllowed(), TIME_ALLOWED);
202     // a) Not too early
203     assertTrue ( "elapsed="+timoutException.getTimeElapsed()+" <= (allowed-resolution)="+(TIME_ALLOWED-counterThread.getResolution()),
204         timoutException.getTimeElapsed() > TIME_ALLOWED-counterThread.getResolution());
205     // b) Not too late.
206     //    This part is problematic in a busy test system, so we just print a warning.
207     //    We already verified that a timeout occurred, we just can't be picky about how long it took.
208     if (timoutException.getTimeElapsed() > maxTime(multiThreaded)) {
209       System.out.println("Informative: timeout exceeded (no action required: most probably just " +
210         " because the test machine is slower than usual):  " +
211         "lastDoc="+exceptionDoc+
212         " ,&& allowed="+timoutException.getTimeAllowed() +
213         " ,&& elapsed="+timoutException.getTimeElapsed() +
214         " >= " + maxTimeStr(multiThreaded));
215     }
216   }
217
218   private long maxTime(boolean multiThreaded) {
219     long res = 2 * counterThread.getResolution() + TIME_ALLOWED + SLOW_DOWN; // some slack for less noise in this test
220     if (multiThreaded) {
221       res *= MULTI_THREAD_SLACK; // larger slack  
222     }
223     return res;
224   }
225
226   private String maxTimeStr(boolean multiThreaded) {
227     String s =
228       "( " +
229       "2*resolution +  TIME_ALLOWED + SLOW_DOWN = " +
230       "2*" + counterThread.getResolution() + " + " + TIME_ALLOWED + " + " + SLOW_DOWN +
231       ")";
232     if (multiThreaded) {
233       s = MULTI_THREAD_SLACK + " * "+s;  
234     }
235     return maxTime(multiThreaded) + " = " + s;
236   }
237
238   /**
239    * Test timeout behavior when resolution is modified. 
240    */
241   public void testModifyResolution() {
242     try {
243       // increase and test
244       long resolution = 20 * TimerThread.DEFAULT_RESOLUTION; //400
245       counterThread.setResolution(resolution);
246       assertEquals(resolution, counterThread.getResolution());
247       doTestTimeout(false,true);
248       // decrease much and test
249       resolution = 5;
250       counterThread.setResolution(resolution);
251       assertEquals(resolution, counterThread.getResolution());
252       doTestTimeout(false,true);
253       // return to default and test
254       resolution = TimerThread.DEFAULT_RESOLUTION;
255       counterThread.setResolution(resolution);
256       assertEquals(resolution, counterThread.getResolution());
257       doTestTimeout(false,true);
258     } finally {
259       counterThread.setResolution(TimerThread.DEFAULT_RESOLUTION);
260     }
261   }
262   
263   /** 
264    * Test correctness with multiple searching threads.
265    */
266   public void testSearchMultiThreaded() throws Exception {
267     doTestMultiThreads(false);
268   }
269
270   /** 
271    * Test correctness with multiple searching threads.
272    */
273   public void testTimeoutMultiThreaded() throws Exception {
274     doTestMultiThreads(true);
275   }
276   
277   private void doTestMultiThreads(final boolean withTimeout) throws Exception {
278     Thread [] threadArray = new Thread[N_THREADS];
279     final BitSet success = new BitSet(N_THREADS);
280     for( int i = 0; i < threadArray.length; ++i ) {
281       final int num = i;
282       threadArray[num] = new Thread() {
283           @Override
284           public void run() {
285             if (withTimeout) {
286               doTestTimeout(true,true);
287             } else {
288               doTestSearch();
289             }
290             synchronized(success) {
291               success.set(num);
292             }
293           }
294       };
295     }
296     for( int i = 0; i < threadArray.length; ++i ) {
297       threadArray[i].start();
298     }
299     for( int i = 0; i < threadArray.length; ++i ) {
300       threadArray[i].join();
301     }
302     assertEquals("some threads failed!", N_THREADS,success.cardinality());
303   }
304   
305   // counting collector that can slow down at collect().
306   private class MyHitCollector extends Collector {
307     private final BitSet bits = new BitSet();
308     private int slowdown = 0;
309     private int lastDocCollected = -1;
310     private int docBase = 0;
311
312     /**
313      * amount of time to wait on each collect to simulate a long iteration
314      */
315     public void setSlowDown( int milliseconds ) {
316       slowdown = milliseconds;
317     }
318     
319     public int hitCount() {
320       return bits.cardinality();
321     }
322
323     public int getLastDocCollected() {
324       return lastDocCollected;
325     }
326
327     @Override
328     public void setScorer(Scorer scorer) throws IOException {
329       // scorer is not needed
330     }
331     
332     @Override
333     public void collect(final int doc) throws IOException {
334       int docId = doc + docBase;
335       if( slowdown > 0 ) {
336         try {
337           Thread.sleep(slowdown);
338         } catch (InterruptedException ie) {
339           throw new ThreadInterruptedException(ie);
340         }
341       }
342       assert docId >= 0: " base=" + docBase + " doc=" + doc;
343       bits.set( docId );
344       lastDocCollected = docId;
345     }
346     
347     @Override
348     public void setNextReader(IndexReader reader, int base) {
349       docBase = base;
350     }
351     
352     @Override
353     public boolean acceptsDocsOutOfOrder() {
354       return false;
355     }
356
357   }
358
359 }