add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / test-framework / org / apache / lucene / search / QueryUtils.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.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.util.Random;
26
27 import junit.framework.Assert;
28
29 import org.apache.lucene.analysis.MockAnalyzer;
30 import org.apache.lucene.document.Document;
31 import org.apache.lucene.index.IndexReader;
32 import org.apache.lucene.index.IndexWriter;
33 import org.apache.lucene.index.IndexWriterConfig;
34 import org.apache.lucene.index.MultiReader;
35 import org.apache.lucene.store.Directory;
36 import org.apache.lucene.store.MockDirectoryWrapper;
37 import org.apache.lucene.store.RAMDirectory;
38 import org.apache.lucene.util._TestUtil;
39
40 import static org.apache.lucene.util.LuceneTestCase.TEST_VERSION_CURRENT;
41
42
43
44
45 public class QueryUtils {
46
47   /** Check the types of things query objects should be able to do. */
48   public static void check(Query q) {
49     checkHashEquals(q);
50   }
51
52   /** check very basic hashCode and equals */
53   public static void checkHashEquals(Query q) {
54     Query q2 = (Query)q.clone();
55     checkEqual(q,q2);
56
57     Query q3 = (Query)q.clone();
58     q3.setBoost(7.21792348f);
59     checkUnequal(q,q3);
60
61     // test that a class check is done so that no exception is thrown
62     // in the implementation of equals()
63     Query whacky = new Query() {
64       @Override
65       public String toString(String field) {
66         return "My Whacky Query";
67       }
68     };
69     whacky.setBoost(q.getBoost());
70     checkUnequal(q, whacky);
71     
72     // null test
73     Assert.assertFalse(q.equals(null));
74   }
75
76   public static void checkEqual(Query q1, Query q2) {
77     Assert.assertEquals(q1, q2);
78     Assert.assertEquals(q1.hashCode(), q2.hashCode());
79   }
80
81   public static void checkUnequal(Query q1, Query q2) {
82     Assert.assertTrue(!q1.equals(q2));
83     Assert.assertTrue(!q2.equals(q1));
84
85     // possible this test can fail on a hash collision... if that
86     // happens, please change test to use a different example.
87     Assert.assertTrue(q1.hashCode() != q2.hashCode());
88   }
89   
90   /** deep check that explanations of a query 'score' correctly */
91   public static void checkExplanations (final Query q, final Searcher s) throws IOException {
92     CheckHits.checkExplanations(q, null, s, true);
93   }
94   
95   /** 
96    * Various query sanity checks on a searcher, some checks are only done for
97    * instanceof IndexSearcher.
98    *
99    * @see #check(Query)
100    * @see #checkFirstSkipTo
101    * @see #checkSkipTo
102    * @see #checkExplanations
103    * @see #checkSerialization
104    * @see #checkEqual
105    */
106   public static void check(Random random, Query q1, Searcher s) {
107     check(random, q1, s, true);
108   }
109   private static void check(Random random, Query q1, Searcher s, boolean wrap) {
110     try {
111       check(q1);
112       if (s!=null) {
113         if (s instanceof IndexSearcher) {
114           IndexSearcher is = (IndexSearcher)s;
115           checkFirstSkipTo(q1,is);
116           checkSkipTo(q1,is);
117           if (wrap) {
118             check(random, q1, wrapUnderlyingReader(random, is, -1), false);
119             check(random, q1, wrapUnderlyingReader(random, is,  0), false);
120             check(random, q1, wrapUnderlyingReader(random, is, +1), false);
121           }
122         }
123         if (wrap) {
124           check(random,q1, wrapSearcher(random, s, -1), false);
125           check(random,q1, wrapSearcher(random, s,  0), false);
126           check(random,q1, wrapSearcher(random, s, +1), false);
127         }
128         checkExplanations(q1,s);
129         checkSerialization(q1,s);
130         
131         Query q2 = (Query)q1.clone();
132         checkEqual(s.rewrite(q1),
133                    s.rewrite(q2));
134       }
135     } catch (IOException e) {
136       throw new RuntimeException(e);
137     }
138   }
139
140   /**
141    * Given an IndexSearcher, returns a new IndexSearcher whose IndexReader 
142    * is a MultiReader containing the Reader of the original IndexSearcher, 
143    * as well as several "empty" IndexReaders -- some of which will have 
144    * deleted documents in them.  This new IndexSearcher should 
145    * behave exactly the same as the original IndexSearcher.
146    * @param s the searcher to wrap
147    * @param edge if negative, s will be the first sub; if 0, s will be in the middle, if positive s will be the last sub
148    */
149   public static IndexSearcher wrapUnderlyingReader(Random random, final IndexSearcher s, final int edge) 
150     throws IOException {
151
152     IndexReader r = s.getIndexReader();
153
154     // we can't put deleted docs before the nested reader, because
155     // it will throw off the docIds
156     IndexReader[] readers = new IndexReader[] {
157       edge < 0 ? r : IndexReader.open(makeEmptyIndex(random, 0), true),
158       IndexReader.open(makeEmptyIndex(random, 0), true),
159       new MultiReader(new IndexReader[] {
160         IndexReader.open(makeEmptyIndex(random, edge < 0 ? 4 : 0), true),
161         IndexReader.open(makeEmptyIndex(random, 0), true),
162         0 == edge ? r : IndexReader.open(makeEmptyIndex(random, 0), true)
163       }),
164       IndexReader.open(makeEmptyIndex(random, 0 < edge ? 0 : 7), true),
165       IndexReader.open(makeEmptyIndex(random, 0), true),
166       new MultiReader(new IndexReader[] {
167         IndexReader.open(makeEmptyIndex(random, 0 < edge ? 0 : 5), true),
168         IndexReader.open(makeEmptyIndex(random, 0), true),
169         0 < edge ? r : IndexReader.open(makeEmptyIndex(random, 0), true)
170       })
171     };
172     IndexSearcher out = new IndexSearcher(new MultiReader(readers));
173     out.setSimilarity(s.getSimilarity());
174     return out;
175   }
176   /**
177    * Given a Searcher, returns a new MultiSearcher wrapping the  
178    * the original Searcher, 
179    * as well as several "empty" IndexSearchers -- some of which will have
180    * deleted documents in them.  This new MultiSearcher 
181    * should behave exactly the same as the original Searcher.
182    * @param s the Searcher to wrap
183    * @param edge if negative, s will be the first sub; if 0, s will be in hte middle, if positive s will be the last sub
184    */
185   public static MultiSearcher wrapSearcher(Random random, final Searcher s, final int edge) 
186     throws IOException {
187
188     // we can't put deleted docs before the nested reader, because
189     // it will through off the docIds
190     Searcher[] searchers = new Searcher[] {
191       edge < 0 ? s : new IndexSearcher(makeEmptyIndex(random, 0), true),
192       new MultiSearcher(new Searcher[] {
193         new IndexSearcher(makeEmptyIndex(random, edge < 0 ? 65 : 0), true),
194         new IndexSearcher(makeEmptyIndex(random, 0), true),
195         0 == edge ? s : new IndexSearcher(makeEmptyIndex(random, 0), true)
196       }),
197       new IndexSearcher(makeEmptyIndex(random, 0 < edge ? 0 : 3), true),
198       new IndexSearcher(makeEmptyIndex(random, 0), true),
199       new MultiSearcher(new Searcher[] {
200         new IndexSearcher(makeEmptyIndex(random, 0 < edge ? 0 : 5), true),
201         new IndexSearcher(makeEmptyIndex(random, 0), true),
202         0 < edge ? s : new IndexSearcher(makeEmptyIndex(random, 0), true)
203       })
204     };
205     MultiSearcher out = new MultiSearcher(searchers);
206     out.setSimilarity(s.getSimilarity());
207     return out;
208   }
209
210   private static Directory makeEmptyIndex(Random random, final int numDeletedDocs) 
211     throws IOException {
212     Directory d = new MockDirectoryWrapper(random, new RAMDirectory());
213       IndexWriter w = new IndexWriter(d, new IndexWriterConfig(
214         TEST_VERSION_CURRENT, new MockAnalyzer(random)));
215
216       for (int i = 0; i < numDeletedDocs; i++) {
217         w.addDocument(new Document());
218       }
219       w.commit();
220       w.deleteDocuments( new MatchAllDocsQuery() );
221       _TestUtil.keepFullyDeletedSegments(w);
222       w.commit();
223
224       if (0 < numDeletedDocs)
225         Assert.assertTrue("writer has no deletions", w.hasDeletions());
226
227       Assert.assertEquals("writer is missing some deleted docs", 
228                           numDeletedDocs, w.maxDoc());
229       Assert.assertEquals("writer has non-deleted docs", 
230                           0, w.numDocs());
231       w.close();
232       IndexReader r = IndexReader.open(d, true);
233       Assert.assertEquals("reader has wrong number of deleted docs", 
234                           numDeletedDocs, r.numDeletedDocs());
235       r.close();
236       return d;
237   }
238   
239
240   /** check that the query weight is serializable. 
241    * @throws IOException if serialization check fail. 
242    */
243   private static void checkSerialization(Query q, Searcher s) throws IOException {
244     Weight w = s.createNormalizedWeight(q);
245     try {
246       ByteArrayOutputStream bos = new ByteArrayOutputStream();
247       ObjectOutputStream oos = new ObjectOutputStream(bos);
248       oos.writeObject(w);
249       oos.close();
250       ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
251       ois.readObject();
252       ois.close();
253       
254       //skip equals() test for now - most weights don't override equals() and we won't add this just for the tests.
255       //TestCase.assertEquals("writeObject(w) != w.  ("+w+")",w2,w);   
256       
257     } catch (Exception e) {
258       IOException e2 = new IOException("Serialization failed for "+w);
259       e2.initCause(e);
260       throw e2;
261     }
262   }
263
264
265   /** alternate scorer skipTo(),skipTo(),next(),next(),skipTo(),skipTo(), etc
266    * and ensure a hitcollector receives same docs and scores
267    */
268   public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOException {
269     //System.out.println("Checking "+q);
270     
271     if (s.createNormalizedWeight(q).scoresDocsOutOfOrder()) return;  // in this case order of skipTo() might differ from that of next().
272
273     final int skip_op = 0;
274     final int next_op = 1;
275     final int orders [][] = {
276         {next_op},
277         {skip_op},
278         {skip_op, next_op},
279         {next_op, skip_op},
280         {skip_op, skip_op, next_op, next_op},
281         {next_op, next_op, skip_op, skip_op},
282         {skip_op, skip_op, skip_op, next_op, next_op},
283     };
284     for (int k = 0; k < orders.length; k++) {
285
286         final int order[] = orders[k];
287         // System.out.print("Order:");for (int i = 0; i < order.length; i++)
288         // System.out.print(order[i]==skip_op ? " skip()":" next()");
289         // System.out.println();
290         final int opidx[] = { 0 };
291         final int lastDoc[] = {-1};
292
293         // FUTURE: ensure scorer.doc()==-1
294
295         final float maxDiff = 1e-5f;
296         final IndexReader lastReader[] = {null};
297
298         s.search(q, new Collector() {
299           private Scorer sc;
300           private IndexReader reader;
301           private Scorer scorer;
302
303           @Override
304           public void setScorer(Scorer scorer) throws IOException {
305             this.sc = scorer;
306           }
307
308           @Override
309           public void collect(int doc) throws IOException {
310             float score = sc.score();
311             lastDoc[0] = doc;
312             try {
313               if (scorer == null) {
314                 Weight w = s.createNormalizedWeight(q);
315                 scorer = w.scorer(reader, true, false);
316               }
317               
318               int op = order[(opidx[0]++) % order.length];
319               // System.out.println(op==skip_op ?
320               // "skip("+(sdoc[0]+1)+")":"next()");
321               boolean more = op == skip_op ? scorer.advance(scorer.docID() + 1) != DocIdSetIterator.NO_MORE_DOCS
322                   : scorer.nextDoc() != DocIdSetIterator.NO_MORE_DOCS;
323               int scorerDoc = scorer.docID();
324               float scorerScore = scorer.score();
325               float scorerScore2 = scorer.score();
326               float scoreDiff = Math.abs(score - scorerScore);
327               float scorerDiff = Math.abs(scorerScore2 - scorerScore);
328               if (!more || doc != scorerDoc || scoreDiff > maxDiff
329                   || scorerDiff > maxDiff) {
330                 StringBuilder sbord = new StringBuilder();
331                 for (int i = 0; i < order.length; i++)
332                   sbord.append(order[i] == skip_op ? " skip()" : " next()");
333                 throw new RuntimeException("ERROR matching docs:" + "\n\t"
334                     + (doc != scorerDoc ? "--> " : "") + "doc=" + doc + ", scorerDoc=" + scorerDoc
335                     + "\n\t" + (!more ? "--> " : "") + "tscorer.more=" + more
336                     + "\n\t" + (scoreDiff > maxDiff ? "--> " : "")
337                     + "scorerScore=" + scorerScore + " scoreDiff=" + scoreDiff
338                     + " maxDiff=" + maxDiff + "\n\t"
339                     + (scorerDiff > maxDiff ? "--> " : "") + "scorerScore2="
340                     + scorerScore2 + " scorerDiff=" + scorerDiff
341                     + "\n\thitCollector.doc=" + doc + " score=" + score
342                     + "\n\t Scorer=" + scorer + "\n\t Query=" + q + "  "
343                     + q.getClass().getName() + "\n\t Searcher=" + s
344                     + "\n\t Order=" + sbord + "\n\t Op="
345                     + (op == skip_op ? " skip()" : " next()"));
346               }
347             } catch (IOException e) {
348               throw new RuntimeException(e);
349             }
350           }
351
352           @Override
353           public void setNextReader(IndexReader reader, int docBase) throws IOException {
354             // confirm that skipping beyond the last doc, on the
355             // previous reader, hits NO_MORE_DOCS
356             if (lastReader[0] != null) {
357               final IndexReader previousReader = lastReader[0];
358               Weight w = new IndexSearcher(previousReader).createNormalizedWeight(q);
359               Scorer scorer = w.scorer(previousReader, true, false);
360               if (scorer != null) {
361                 boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
362                 Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
363               }
364             }
365             this.reader = lastReader[0] = reader;
366             this.scorer = null;
367             lastDoc[0] = -1;
368           }
369
370           @Override
371           public boolean acceptsDocsOutOfOrder() {
372             return true;
373           }
374         });
375
376         if (lastReader[0] != null) {
377           // confirm that skipping beyond the last doc, on the
378           // previous reader, hits NO_MORE_DOCS
379           final IndexReader previousReader = lastReader[0];
380           Weight w = new IndexSearcher(previousReader).createNormalizedWeight(q);
381           Scorer scorer = w.scorer(previousReader, true, false);
382           if (scorer != null) {
383             boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
384             Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
385           }
386         }
387       }
388   }
389     
390   // check that first skip on just created scorers always goes to the right doc
391   private static void checkFirstSkipTo(final Query q, final IndexSearcher s) throws IOException {
392     //System.out.println("checkFirstSkipTo: "+q);
393     final float maxDiff = 1e-3f;
394     final int lastDoc[] = {-1};
395     final IndexReader lastReader[] = {null};
396
397     s.search(q,new Collector() {
398       private Scorer scorer;
399       private IndexReader reader;
400       @Override
401       public void setScorer(Scorer scorer) throws IOException {
402         this.scorer = scorer;
403       }
404       @Override
405       public void collect(int doc) throws IOException {
406         //System.out.println("doc="+doc);
407         float score = scorer.score();
408         try {
409           
410           for (int i=lastDoc[0]+1; i<=doc; i++) {
411             Weight w = s.createNormalizedWeight(q);
412             Scorer scorer = w.scorer(reader, true, false);
413             Assert.assertTrue("query collected "+doc+" but skipTo("+i+") says no more docs!",scorer.advance(i) != DocIdSetIterator.NO_MORE_DOCS);
414             Assert.assertEquals("query collected "+doc+" but skipTo("+i+") got to "+scorer.docID(),doc,scorer.docID());
415             float skipToScore = scorer.score();
416             Assert.assertEquals("unstable skipTo("+i+") score!",skipToScore,scorer.score(),maxDiff); 
417             Assert.assertEquals("query assigned doc "+doc+" a score of <"+score+"> but skipTo("+i+") has <"+skipToScore+">!",score,skipToScore,maxDiff);
418           }
419           lastDoc[0] = doc;
420         } catch (IOException e) {
421           throw new RuntimeException(e);
422         }
423       }
424
425       @Override
426       public void setNextReader(IndexReader reader, int docBase) throws IOException {
427         // confirm that skipping beyond the last doc, on the
428         // previous reader, hits NO_MORE_DOCS
429         if (lastReader[0] != null) {
430           final IndexReader previousReader = lastReader[0];
431           Weight w = new IndexSearcher(previousReader).createNormalizedWeight(q);
432           Scorer scorer = w.scorer(previousReader, true, false);
433
434           if (scorer != null) {
435             boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
436             Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
437           }
438         }
439
440         this.reader = lastReader[0] = reader;
441         lastDoc[0] = -1;
442       }
443       @Override
444       public boolean acceptsDocsOutOfOrder() {
445         return false;
446       }
447     });
448
449     if (lastReader[0] != null) {
450       // confirm that skipping beyond the last doc, on the
451       // previous reader, hits NO_MORE_DOCS
452       final IndexReader previousReader = lastReader[0];
453       Weight w = new IndexSearcher(previousReader).createNormalizedWeight(q);
454       Scorer scorer = w.scorer(previousReader, true, false);
455       if (scorer != null) {
456         boolean more = scorer.advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
457         Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
458       }
459     }
460   }
461 }