add basic equivalence tests for core query matching (TODO: add spans)

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1292864 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Robert Muir 2012-02-23 17:43:30 +00:00
parent c618516338
commit 30d2fc63c8
2 changed files with 379 additions and 0 deletions

View File

@ -0,0 +1,172 @@
package org.apache.lucene.search;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Basic equivalence tests for core queries
*/
public class TestSimpleSearchEquivalence extends SearchEquivalenceTestBase {
// TODO: we could go a little crazy for a lot of these,
// but these are just simple minimal cases in case something
// goes horribly wrong. Put more intense tests elsewhere.
/** A ⊆ (A B) */
public void testTermVersusBooleanOr() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
TermQuery q1 = new TermQuery(t1);
BooleanQuery q2 = new BooleanQuery();
q2.add(new TermQuery(t1), Occur.SHOULD);
q2.add(new TermQuery(t2), Occur.SHOULD);
assertSubsetOf(q1, q2);
}
/** A ⊆ (+A B) */
public void testTermVersusBooleanReqOpt() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
TermQuery q1 = new TermQuery(t1);
BooleanQuery q2 = new BooleanQuery();
q2.add(new TermQuery(t1), Occur.MUST);
q2.add(new TermQuery(t2), Occur.SHOULD);
assertSubsetOf(q1, q2);
}
/** (A -B) ⊆ A */
public void testBooleanReqExclVersusTerm() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
BooleanQuery q1 = new BooleanQuery();
q1.add(new TermQuery(t1), Occur.MUST);
q1.add(new TermQuery(t2), Occur.MUST_NOT);
TermQuery q2 = new TermQuery(t1);
assertSubsetOf(q1, q2);
}
/** (+A +B) ⊆ (A B) */
public void testBooleanAndVersusBooleanOr() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
BooleanQuery q1 = new BooleanQuery();
q1.add(new TermQuery(t1), Occur.SHOULD);
q1.add(new TermQuery(t2), Occur.SHOULD);
BooleanQuery q2 = new BooleanQuery();
q2.add(new TermQuery(t1), Occur.SHOULD);
q2.add(new TermQuery(t2), Occur.SHOULD);
assertSubsetOf(q1, q2);
}
/** (A B) = (A | B) */
public void testDisjunctionSumVersusDisjunctionMax() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
BooleanQuery q1 = new BooleanQuery();
q1.add(new TermQuery(t1), Occur.SHOULD);
q1.add(new TermQuery(t2), Occur.SHOULD);
DisjunctionMaxQuery q2 = new DisjunctionMaxQuery(0.5f);
q2.add(new TermQuery(t1));
q2.add(new TermQuery(t2));
assertSameSet(q1, q2);
}
/** "A B" ⊆ (+A +B) */
public void testExactPhraseVersusBooleanAnd() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
PhraseQuery q1 = new PhraseQuery();
q1.add(t1);
q1.add(t2);
BooleanQuery q2 = new BooleanQuery();
q2.add(new TermQuery(t1), Occur.MUST);
q2.add(new TermQuery(t2), Occur.MUST);
assertSubsetOf(q1, q2);
}
/** same as above, with posincs */
public void testExactPhraseVersusBooleanAndWithHoles() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
PhraseQuery q1 = new PhraseQuery();
q1.add(t1);
q1.add(t2, 2);
BooleanQuery q2 = new BooleanQuery();
q2.add(new TermQuery(t1), Occur.MUST);
q2.add(new TermQuery(t2), Occur.MUST);
assertSubsetOf(q1, q2);
}
/** "A B" ⊆ "A B"~1 */
public void testPhraseVersusSloppyPhrase() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
PhraseQuery q1 = new PhraseQuery();
q1.add(t1);
q1.add(t2);
PhraseQuery q2 = new PhraseQuery();
q2.add(t1);
q2.add(t2);
q2.setSlop(1);
assertSubsetOf(q1, q2);
}
/** same as above, with posincs */
public void testPhraseVersusSloppyPhraseWithHoles() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
PhraseQuery q1 = new PhraseQuery();
q1.add(t1);
q1.add(t2, 2);
PhraseQuery q2 = new PhraseQuery();
q2.add(t1);
q2.add(t2, 2);
q2.setSlop(1);
assertSubsetOf(q1, q2);
}
/** "A B" ⊆ "A (B C)" */
public void testExactPhraseVersusMultiPhrase() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
PhraseQuery q1 = new PhraseQuery();
q1.add(t1);
q1.add(t2);
Term t3 = randomTerm();
MultiPhraseQuery q2 = new MultiPhraseQuery();
q2.add(t1);
q2.add(new Term[] { t2, t3 });
assertSubsetOf(q1, q2);
}
/** same as above, with posincs */
public void testExactPhraseVersusMultiPhraseWithHoles() throws Exception {
Term t1 = randomTerm();
Term t2 = randomTerm();
PhraseQuery q1 = new PhraseQuery();
q1.add(t1);
q1.add(t2, 2);
Term t3 = randomTerm();
MultiPhraseQuery q2 = new MultiPhraseQuery();
q2.add(t1);
q2.add(new Term[] { t2, t3 }, 2);
assertSubsetOf(q1, q2);
}
}

View File

@ -0,0 +1,207 @@
package org.apache.lucene.search;
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.BitSet;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.analysis.MockTokenizer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util._TestUtil;
import org.apache.lucene.util.automaton.BasicAutomata;
import org.apache.lucene.util.automaton.CharacterRunAutomaton;
import org.junit.AfterClass;
import org.junit.BeforeClass;
/**
* Simple base class for checking search equivalence.
* Extend it, and write tests that create {@link #randomTerm()}s
* (all terms are single characters a-z), and use
* {@link #assertSameSet(Query, Query)} and
* {@link #assertSubsetOf(Query, Query)}
*/
public abstract class SearchEquivalenceTestBase extends LuceneTestCase {
protected static IndexSearcher s1, s2;
protected static Directory directory;
protected static IndexReader reader;
protected static Analyzer analyzer;
protected static String stopword; // we always pick a character as a stopword
@BeforeClass
public static void beforeClass() throws Exception {
directory = newDirectory();
stopword = "" + randomChar();
CharacterRunAutomaton stopset = new CharacterRunAutomaton(BasicAutomata.makeString(stopword));
analyzer = new MockAnalyzer(random, MockTokenizer.WHITESPACE, false, stopset, true);
RandomIndexWriter iw = new RandomIndexWriter(random, directory, analyzer);
Document doc = new Document();
Field id = new StringField("id", "");
Field field = new TextField("field", "");
doc.add(id);
doc.add(field);
// index some docs
int numDocs = atLeast(1000);
for (int i = 0; i < numDocs; i++) {
id.setStringValue(Integer.toString(i));
field.setStringValue(randomFieldContents());
iw.addDocument(doc);
}
// delete some docs
int numDeletes = numDocs/20;
for (int i = 0; i < numDeletes; i++) {
Term toDelete = new Term("id", Integer.toString(random.nextInt(numDocs)));
if (random.nextBoolean()) {
iw.deleteDocuments(toDelete);
} else {
iw.deleteDocuments(new TermQuery(toDelete));
}
}
reader = iw.getReader();
s1 = newSearcher(reader);
s2 = newSearcher(reader);
iw.close();
}
@AfterClass
public static void afterClass() throws Exception {
reader.close();
directory.close();
analyzer.close();
reader = null;
directory = null;
analyzer = null;
s1 = s2 = null;
}
/**
* populate a field with random contents.
* terms should be single characters in lowercase (a-z)
* tokenization can be assumed to be on whitespace.
*/
static String randomFieldContents() {
// TODO: zipf-like distribution
StringBuilder sb = new StringBuilder();
int numTerms = random.nextInt(15);
for (int i = 0; i < numTerms; i++) {
if (sb.length() > 0) {
sb.append(' '); // whitespace
}
sb.append(randomChar());
}
return sb.toString();
}
/**
* returns random character (a-z)
*/
static char randomChar() {
return (char) _TestUtil.nextInt(random, 'a', 'z');
}
/**
* returns a term suitable for searching.
* terms are single characters in lowercase (a-z)
*/
protected Term randomTerm() {
return new Term("field", "" + randomChar());
}
/**
* Returns a random filter over the document set
*/
protected Filter randomFilter() {
return new QueryWrapperFilter(TermRangeQuery.newStringRange("field", "a", "" + randomChar(), true, true));
}
/**
* Asserts that the documents returned by <code>q1</code>
* are the same as of those returned by <code>q2</code>
*/
public void assertSameSet(Query q1, Query q2) throws Exception {
assertSubsetOf(q1, q2);
assertSubsetOf(q2, q1);
}
/**
* Asserts that the documents returned by <code>q1</code>
* are a subset of those returned by <code>q2</code>
*/
public void assertSubsetOf(Query q1, Query q2) throws Exception {
// test without a filter
assertSubsetOf(q1, q2, null);
// test with a filter (this will sometimes cause advance'ing enough to test it)
assertSubsetOf(q1, q2, randomFilter());
}
/**
* Asserts that the documents returned by <code>q1</code>
* are a subset of those returned by <code>q2</code>.
*
* Both queries will be filtered by <code>filter</code>
*/
protected void assertSubsetOf(Query q1, Query q2, Filter filter) throws Exception {
// TRUNK ONLY: test both filter code paths
if (filter != null && random.nextBoolean()) {
final boolean q1RandomAccess = random.nextBoolean();
final boolean q2RandomAccess = random.nextBoolean();
q1 = new FilteredQuery(q1, filter) {
@Override
protected boolean useRandomAccess(Bits bits, int firstFilterDoc) {
return q1RandomAccess;
}
};
q2 = new FilteredQuery(q2, filter) {
@Override
protected boolean useRandomAccess(Bits bits, int firstFilterDoc) {
return q2RandomAccess;
}
};
filter = null;
}
// not efficient, but simple!
TopDocs td1 = s1.search(q1, filter, reader.maxDoc());
TopDocs td2 = s2.search(q2, filter, reader.maxDoc());
assertTrue(td1.totalHits <= td2.totalHits);
// fill the superset into a bitset
BitSet bitset = new BitSet();
for (int i = 0; i < td2.scoreDocs.length; i++) {
bitset.set(td2.scoreDocs[i].doc);
}
// check in the subset, that every bit was set by the super
for (int i = 0; i < td1.scoreDocs.length; i++) {
assertTrue(bitset.get(td1.scoreDocs[i].doc));
}
}
}