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