001: package org.apache.lucene.search;
002:
003: import junit.framework.TestCase;
004:
005: import java.io.ByteArrayInputStream;
006: import java.io.ByteArrayOutputStream;
007: import java.io.IOException;
008: import java.io.ObjectInputStream;
009: import java.io.ObjectOutputStream;
010:
011: /**
012: * Copyright 2005 Apache Software Foundation
013: *
014: * Licensed under the Apache License, Version 2.0 (the "License");
015: * you may not use this file except in compliance with the License.
016: * You may obtain a copy of the License at
017: *
018: * http://www.apache.org/licenses/LICENSE-2.0
019: *
020: * Unless required by applicable law or agreed to in writing, software
021: * distributed under the License is distributed on an "AS IS" BASIS,
022: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
023: * See the License for the specific language governing permissions and
024: * limitations under the License.
025: */
026:
027: /**
028: * @author yonik
029: */
030: public class QueryUtils {
031:
032: /** Check the types of things query objects should be able to do. */
033: public static void check(Query q) {
034: checkHashEquals(q);
035: }
036:
037: /** check very basic hashCode and equals */
038: public static void checkHashEquals(Query q) {
039: Query q2 = (Query) q.clone();
040: checkEqual(q, q2);
041:
042: Query q3 = (Query) q.clone();
043: q3.setBoost(7.21792348f);
044: checkUnequal(q, q3);
045:
046: // test that a class check is done so that no exception is thrown
047: // in the implementation of equals()
048: Query whacky = new Query() {
049: public String toString(String field) {
050: return "My Whacky Query";
051: }
052: };
053: whacky.setBoost(q.getBoost());
054: checkUnequal(q, whacky);
055: }
056:
057: public static void checkEqual(Query q1, Query q2) {
058: TestCase.assertEquals(q1, q2);
059: TestCase.assertEquals(q1.hashCode(), q2.hashCode());
060: }
061:
062: public static void checkUnequal(Query q1, Query q2) {
063: TestCase.assertTrue(!q1.equals(q2));
064: TestCase.assertTrue(!q2.equals(q1));
065:
066: // possible this test can fail on a hash collision... if that
067: // happens, please change test to use a different example.
068: TestCase.assertTrue(q1.hashCode() != q2.hashCode());
069: }
070:
071: /** deep check that explanations of a query 'score' correctly */
072: public static void checkExplanations(final Query q, final Searcher s)
073: throws IOException {
074: CheckHits.checkExplanations(q, null, s, true);
075: }
076:
077: /**
078: * various query sanity checks on a searcher, including explanation checks.
079: * @see #checkExplanations
080: * @see #checkSkipTo
081: * @see #check(Query)
082: */
083: public static void check(Query q1, Searcher s) {
084: try {
085: check(q1);
086: if (s != null) {
087: if (s instanceof IndexSearcher) {
088: IndexSearcher is = (IndexSearcher) s;
089: checkFirstSkipTo(q1, is);
090: checkSkipTo(q1, is);
091: }
092: checkExplanations(q1, s);
093: checkSerialization(q1, s);
094: }
095: } catch (IOException e) {
096: throw new RuntimeException(e);
097: }
098: }
099:
100: /** check that the query weight is serializable.
101: * @throws IOException if serialization check fail.
102: */
103: private static void checkSerialization(Query q, Searcher s)
104: throws IOException {
105: Weight w = q.weight(s);
106: try {
107: ByteArrayOutputStream bos = new ByteArrayOutputStream();
108: ObjectOutputStream oos = new ObjectOutputStream(bos);
109: oos.writeObject(w);
110: oos.close();
111: ObjectInputStream ois = new ObjectInputStream(
112: new ByteArrayInputStream(bos.toByteArray()));
113: ois.readObject();
114: ois.close();
115:
116: //skip rquals() test for now - most weights don't overide equals() and we won't add this just for the tests.
117: //TestCase.assertEquals("writeObject(w) != w. ("+w+")",w2,w);
118:
119: } catch (Exception e) {
120: IOException e2 = new IOException(
121: "Serialization failed for " + w);
122: e2.initCause(e);
123: throw e2;
124: }
125: }
126:
127: /** alternate scorer skipTo(),skipTo(),next(),next(),skipTo(),skipTo(), etc
128: * and ensure a hitcollector receives same docs and scores
129: */
130: public static void checkSkipTo(final Query q, final IndexSearcher s)
131: throws IOException {
132: //System.out.println("Checking "+q);
133:
134: if (BooleanQuery.getAllowDocsOutOfOrder())
135: return; // in this case order of skipTo() might differ from that of next().
136:
137: final int skip_op = 0;
138: final int next_op = 1;
139: final int orders[][] = { { next_op }, { skip_op },
140: { skip_op, next_op }, { next_op, skip_op },
141: { skip_op, skip_op, next_op, next_op },
142: { next_op, next_op, skip_op, skip_op },
143: { skip_op, skip_op, skip_op, next_op, next_op }, };
144: for (int k = 0; k < orders.length; k++) {
145: final int order[] = orders[k];
146: //System.out.print("Order:");for (int i = 0; i < order.length; i++) System.out.print(order[i]==skip_op ? " skip()":" next()"); System.out.println();
147: final int opidx[] = { 0 };
148:
149: final Weight w = q.weight(s);
150: final Scorer scorer = w.scorer(s.getIndexReader());
151:
152: // FUTURE: ensure scorer.doc()==-1
153:
154: final int[] sdoc = new int[] { -1 };
155: final float maxDiff = 1e-5f;
156: s.search(q, new HitCollector() {
157: public void collect(int doc, float score) {
158: try {
159: int op = order[(opidx[0]++) % order.length];
160: //System.out.println(op==skip_op ? "skip("+(sdoc[0]+1)+")":"next()");
161: boolean more = op == skip_op ? scorer
162: .skipTo(sdoc[0] + 1) : scorer.next();
163: sdoc[0] = scorer.doc();
164: float scorerScore = scorer.score();
165: float scorerScore2 = scorer.score();
166: float scoreDiff = Math.abs(score - scorerScore);
167: float scorerDiff = Math.abs(scorerScore2
168: - scorerScore);
169: if (!more || doc != sdoc[0]
170: || scoreDiff > maxDiff
171: || scorerDiff > maxDiff) {
172: StringBuffer sbord = new StringBuffer();
173: for (int i = 0; i < order.length; i++)
174: sbord
175: .append(order[i] == skip_op ? " skip()"
176: : " next()");
177: throw new RuntimeException(
178: "ERROR matching docs:" + "\n\t"
179: + (doc != sdoc[0] ? "--> "
180: : "")
181: + "doc="
182: + sdoc[0]
183: + "\n\t"
184: + (!more ? "--> " : "")
185: + "tscorer.more="
186: + more
187: + "\n\t"
188: + (scoreDiff > maxDiff ? "--> "
189: : "")
190: + "scorerScore="
191: + scorerScore
192: + " scoreDiff="
193: + scoreDiff
194: + " maxDiff="
195: + maxDiff
196: + "\n\t"
197: + (scorerDiff > maxDiff ? "--> "
198: : "")
199: + "scorerScore2="
200: + scorerScore2
201: + " scorerDiff="
202: + scorerDiff
203: + "\n\thitCollector.doc="
204: + doc
205: + " score="
206: + score
207: + "\n\t Scorer="
208: + scorer
209: + "\n\t Query="
210: + q
211: + " "
212: + q.getClass().getName()
213: + "\n\t Searcher="
214: + s
215: + "\n\t Order="
216: + sbord
217: + "\n\t Op="
218: + (op == skip_op ? " skip()"
219: : " next()"));
220: }
221: } catch (IOException e) {
222: throw new RuntimeException(e);
223: }
224: }
225: });
226:
227: // make sure next call to scorer is false.
228: int op = order[(opidx[0]++) % order.length];
229: //System.out.println(op==skip_op ? "last: skip()":"last: next()");
230: boolean more = op == skip_op ? scorer.skipTo(sdoc[0] + 1)
231: : scorer.next();
232: TestCase.assertFalse(more);
233: }
234: }
235:
236: // check that first skip on just created scorers always goes to the right doc
237: private static void checkFirstSkipTo(final Query q,
238: final IndexSearcher s) throws IOException {
239: //System.out.println("checkFirstSkipTo: "+q);
240: final float maxDiff = 1e-5f;
241: final int lastDoc[] = { -1 };
242: s.search(q, new HitCollector() {
243: public void collect(int doc, float score) {
244: //System.out.println("doc="+doc);
245: try {
246: for (int i = lastDoc[0] + 1; i <= doc; i++) {
247: Weight w = q.weight(s);
248: Scorer scorer = w.scorer(s.getIndexReader());
249: TestCase.assertTrue("query collected " + doc
250: + " but skipTo(" + i
251: + ") says no more docs!", scorer
252: .skipTo(i));
253: TestCase.assertEquals("query collected " + doc
254: + " but skipTo(" + i + ") got to "
255: + scorer.doc(), doc, scorer.doc());
256: float skipToScore = scorer.score();
257: TestCase.assertEquals("unstable skipTo(" + i
258: + ") score!", skipToScore, scorer
259: .score(), maxDiff);
260: TestCase.assertEquals("query assigned doc "
261: + doc + " a score of <" + score
262: + "> but skipTo(" + i + ") has <"
263: + skipToScore + ">!", score,
264: skipToScore, maxDiff);
265: }
266: lastDoc[0] = doc;
267: } catch (IOException e) {
268: throw new RuntimeException(e);
269: }
270: }
271: });
272: Weight w = q.weight(s);
273: Scorer scorer = w.scorer(s.getIndexReader());
274: boolean more = scorer.skipTo(lastDoc[0] + 1);
275: if (more)
276: TestCase.assertFalse("query's last doc was " + lastDoc[0]
277: + " but skipTo(" + (lastDoc[0] + 1) + ") got to "
278: + scorer.doc(), more);
279: }
280: }
|