Index: common-build.xml =================================================================== --- common-build.xml (revision 836280) +++ common-build.xml (working copy) @@ -48,9 +48,12 @@ + + + Index: CHANGES.txt =================================================================== --- CHANGES.txt (revision 836280) +++ CHANGES.txt (working copy) @@ -155,6 +155,10 @@ New features +* LUCENE-2037: Incorporate JUnit4 in tests. Cloned LuceneTestCase and + modified all tests in search/function to use the Jnit4-style tests + as an exemplar (Erick Erickson) + * LUCENE-1933: Provide a convenience AttributeFactory that creates a Token instance for all basic attributes. (Uwe Schindler) Index: src/test/org/apache/lucene/search/function/TestOrdValues.java =================================================================== --- src/test/org/apache/lucene/search/function/TestOrdValues.java (revision 836300) +++ src/test/org/apache/lucene/search/function/TestOrdValues.java (working copy) @@ -19,42 +19,42 @@ import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexReader; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.QueryUtils; -import org.apache.lucene.search.ScoreDoc; -import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.*; +import static org.junit.Assert.*; +import org.junit.Test; /** * Test search based on OrdFieldSource and ReverseOrdFieldSource. - *

+ *

* Tests here create an index with a few documents, each having * an indexed "id" field. * The ord values of this field are later used for scoring. - *

+ *

* The order tests use Hits to verify that docs are ordered as expected. - *

- * The exact score tests use TopDocs top to verify the exact score. + *

+ * The exact score tests use TopDocs top to verify the exact score. */ +@SuppressWarnings({"UseOfSystemOutOrSystemErr"}) public class TestOrdValues extends FunctionTestSetup { - /* @override constructor */ - public TestOrdValues(String name) { - super(name); + /** + * Test OrdFieldSource + */ + @Test + public void testOrdFieldRank() throws CorruptIndexException, Exception { + doTestRank(ID_FIELD, true); } - /** Test OrdFieldSource */ - public void testOrdFieldRank () throws CorruptIndexException, Exception { - doTestRank(ID_FIELD,true); + /** + * Test ReverseOrdFieldSource + */ + @Test + public void testReverseOrdFieldRank() throws CorruptIndexException, Exception { + doTestRank(ID_FIELD, false); } - /** Test ReverseOrdFieldSource */ - public void testReverseOrdFieldRank () throws CorruptIndexException, Exception { - doTestRank(ID_FIELD,false); - } - // Test that queries based on reverse/ordFieldScore scores correctly - private void doTestRank (String field, boolean inOrder) throws CorruptIndexException, Exception { + private void doTestRank(String field, boolean inOrder) throws CorruptIndexException, Exception { IndexSearcher s = new IndexSearcher(dir, true); ValueSource vs; if (inOrder) { @@ -62,42 +62,48 @@ } else { vs = new ReverseOrdFieldSource(field); } - + Query q = new ValueSourceQuery(vs); - log("test: "+q); - QueryUtils.check(q,s); + log("test: " + q); + QueryUtils.check(q, s); ScoreDoc[] h = s.search(q, null, 1000).scoreDocs; - assertEquals("All docs should be matched!",N_DOCS,h.length); + assertEquals("All docs should be matched!", N_DOCS, h.length); String prevID = inOrder - ? "IE" // greater than all ids of docs in this test ("ID0001", etc.) - : "IC"; // smaller than all ids of docs in this test ("ID0001", etc.) - - for (int i=0; i prev res id "+prevID, resID.compareTo(prevID)>0); + assertTrue("res id " + resID + " should be > prev res id " + prevID, resID.compareTo(prevID) > 0); } prevID = resID; } } - /** Test exact score for OrdFieldSource */ - public void testOrdFieldExactScore () throws CorruptIndexException, Exception { - doTestExactScore(ID_FIELD,true); + /** + * Test exact score for OrdFieldSource + */ + @Test + public void testOrdFieldExactScore() throws CorruptIndexException, Exception { + doTestExactScore(ID_FIELD, true); } - /** Test exact score for ReverseOrdFieldSource */ - public void testReverseOrdFieldExactScore () throws CorruptIndexException, Exception { - doTestExactScore(ID_FIELD,false); + /** + * Test exact score for ReverseOrdFieldSource + */ + @Test + public void testReverseOrdFieldExactScore() throws CorruptIndexException, Exception { + doTestExactScore(ID_FIELD, false); } - + // Test that queries based on reverse/ordFieldScore returns docs with expected score. - private void doTestExactScore (String field, boolean inOrder) throws CorruptIndexException, Exception { + private void doTestExactScore(String field, boolean inOrder) throws CorruptIndexException, Exception { IndexSearcher s = new IndexSearcher(dir, true); ValueSource vs; if (inOrder) { @@ -106,41 +112,47 @@ vs = new ReverseOrdFieldSource(field); } Query q = new ValueSourceQuery(vs); - TopDocs td = s.search(q,null,1000); - assertEquals("All docs should be matched!",N_DOCS,td.totalHits); + TopDocs td = s.search(q, null, 1000); + assertEquals("All docs should be matched!", N_DOCS, td.totalHits); ScoreDoc sd[] = td.scoreDocs; - for (int i=0; i * The exact score tests use TopDocs top to verify the exact score. */ +@SuppressWarnings({"UseOfSystemOutOrSystemErr"}) public class TestFieldScoreQuery extends FunctionTestSetup { - /* @override constructor */ - public TestFieldScoreQuery(String name) { - super(name); - } - /** Test that FieldScoreQuery of Type.BYTE returns docs in expected order. */ - public void testRankByte () throws CorruptIndexException, Exception { + @Test + public void testRankByte () throws Exception { // INT field values are small enough to be parsed as byte doTestRank(INT_FIELD,FieldScoreQuery.Type.BYTE); } /** Test that FieldScoreQuery of Type.SHORT returns docs in expected order. */ - public void testRankShort () throws CorruptIndexException, Exception { + @Test + public void testRankShort () throws Exception { // INT field values are small enough to be parsed as short doTestRank(INT_FIELD,FieldScoreQuery.Type.SHORT); } /** Test that FieldScoreQuery of Type.INT returns docs in expected order. */ - public void testRankInt () throws CorruptIndexException, Exception { + @Test + public void testRankInt () throws Exception { doTestRank(INT_FIELD,FieldScoreQuery.Type.INT); } /** Test that FieldScoreQuery of Type.FLOAT returns docs in expected order. */ - public void testRankFloat () throws CorruptIndexException, Exception { + @Test + public void testRankFloat () throws Exception { // INT field can be parsed as float doTestRank(INT_FIELD,FieldScoreQuery.Type.FLOAT); // same values, but in flot format @@ -71,7 +73,7 @@ } // Test that FieldScoreQuery returns docs in expected order. - private void doTestRank (String field, FieldScoreQuery.Type tp) throws CorruptIndexException, Exception { + private void doTestRank (String field, FieldScoreQuery.Type tp) throws Exception { IndexSearcher s = new IndexSearcher(dir, true); Query q = new FieldScoreQuery(field,tp); log("test: "+q); @@ -89,24 +91,28 @@ } /** Test that FieldScoreQuery of Type.BYTE returns the expected scores. */ - public void testExactScoreByte () throws CorruptIndexException, Exception { + @Test + public void testExactScoreByte () throws Exception { // INT field values are small enough to be parsed as byte doTestExactScore(INT_FIELD,FieldScoreQuery.Type.BYTE); } /** Test that FieldScoreQuery of Type.SHORT returns the expected scores. */ - public void testExactScoreShort () throws CorruptIndexException, Exception { + @Test + public void testExactScoreShort () throws Exception { // INT field values are small enough to be parsed as short doTestExactScore(INT_FIELD,FieldScoreQuery.Type.SHORT); } /** Test that FieldScoreQuery of Type.INT returns the expected scores. */ - public void testExactScoreInt () throws CorruptIndexException, Exception { + @Test + public void testExactScoreInt () throws Exception { doTestExactScore(INT_FIELD,FieldScoreQuery.Type.INT); } /** Test that FieldScoreQuery of Type.FLOAT returns the expected scores. */ - public void testExactScoreFloat () throws CorruptIndexException, Exception { + @Test + public void testExactScoreFloat () throws Exception { // INT field can be parsed as float doTestExactScore(INT_FIELD,FieldScoreQuery.Type.FLOAT); // same values, but in flot format @@ -114,40 +120,44 @@ } // Test that FieldScoreQuery returns docs with expected score. - private void doTestExactScore (String field, FieldScoreQuery.Type tp) throws CorruptIndexException, Exception { + private void doTestExactScore (String field, FieldScoreQuery.Type tp) throws Exception { IndexSearcher s = new IndexSearcher(dir, true); Query q = new FieldScoreQuery(field,tp); TopDocs td = s.search(q,null,1000); assertEquals("All docs should be matched!",N_DOCS,td.totalHits); ScoreDoc sd[] = td.scoreDocs; - for (int i=0; i 7.0 - assertEquals("score of "+id+" shuould be "+expectedScore+" != "+score, expectedScore, score, TEST_SCORE_TOLERANCE_DELTA); + assertEquals("score of " + id + " shuould be " + expectedScore + " != " + score, expectedScore, score, TEST_SCORE_TOLERANCE_DELTA); } } /** Test that FieldScoreQuery of Type.BYTE caches/reuses loaded values and consumes the proper RAM resources. */ - public void testCachingByte () throws CorruptIndexException, Exception { + @Test + public void testCachingByte () throws Exception { // INT field values are small enough to be parsed as byte doTestCaching(INT_FIELD,FieldScoreQuery.Type.BYTE); } /** Test that FieldScoreQuery of Type.SHORT caches/reuses loaded values and consumes the proper RAM resources. */ - public void testCachingShort () throws CorruptIndexException, Exception { + @Test + public void testCachingShort () throws Exception { // INT field values are small enough to be parsed as short doTestCaching(INT_FIELD,FieldScoreQuery.Type.SHORT); } /** Test that FieldScoreQuery of Type.INT caches/reuses loaded values and consumes the proper RAM resources. */ - public void testCachingInt () throws CorruptIndexException, Exception { + @Test + public void testCachingInt () throws Exception { doTestCaching(INT_FIELD,FieldScoreQuery.Type.INT); } /** Test that FieldScoreQuery of Type.FLOAT caches/reuses loaded values and consumes the proper RAM resources. */ - public void testCachingFloat () throws CorruptIndexException, Exception { + @Test + public void testCachingFloat () throws Exception { // INT field values can be parsed as float doTestCaching(INT_FIELD,FieldScoreQuery.Type.FLOAT); // same values, but in flot format @@ -155,9 +165,9 @@ } // Test that values loaded for FieldScoreQuery are cached properly and consumes the proper RAM resources. - private void doTestCaching (String field, FieldScoreQuery.Type tp) throws CorruptIndexException, Exception { + private void doTestCaching (String field, FieldScoreQuery.Type tp) throws Exception { // prepare expected array types for comparison - HashMap expectedArrayTypes = new HashMap(); + Map expectedArrayTypes = new HashMap(); expectedArrayTypes.put(FieldScoreQuery.Type.BYTE, new byte[0]); expectedArrayTypes.put(FieldScoreQuery.Type.SHORT, new short[0]); expectedArrayTypes.put(FieldScoreQuery.Type.INT, new int[0]); @@ -223,7 +233,7 @@ } private String testName() { - return getClass().getName()+"."+getName(); + return getClass().getName()+"."+ getName(); } } Index: src/test/org/apache/lucene/search/function/FunctionTestSetup.java =================================================================== --- src/test/org/apache/lucene/search/function/FunctionTestSetup.java (revision 836300) +++ src/test/org/apache/lucene/search/function/FunctionTestSetup.java (working copy) @@ -25,20 +25,23 @@ import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; +import org.apache.lucene.util.LuceneTestCaseJ4; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; -import org.apache.lucene.util.LuceneTestCase; - /** * Setup for function tests */ -public abstract class FunctionTestSetup extends LuceneTestCase { +@Ignore +public class FunctionTestSetup extends LuceneTestCaseJ4 { /** * Actual score computation order is slightly different than assumptios * this allows for a small amount of variation */ - public static float TEST_SCORE_TOLERANCE_DELTA = 0.001f; - + protected static float TEST_SCORE_TOLERANCE_DELTA = 0.001f; + protected static final boolean DBG = false; // change to true for logging to print protected static final int N_DOCS = 17; // select a primary number > 2 @@ -47,62 +50,57 @@ protected static final String TEXT_FIELD = "text"; protected static final String INT_FIELD = "iii"; protected static final String FLOAT_FIELD = "fff"; - + private static final String DOC_TEXT_LINES[] = { - "Well, this is just some plain text we use for creating the ", - "test documents. It used to be a text from an online collection ", - "devoted to first aid, but if there was there an (online) lawyers ", - "first aid collection with legal advices, \"it\" might have quite ", - "probably advised one not to include \"it\"'s text or the text of ", - "any other online collection in one's code, unless one has money ", - "that one don't need and one is happy to donate for lawyers ", - "charity. Anyhow at some point, rechecking the usage of this text, ", - "it became uncertain that this text is free to use, because ", - "the web site in the disclaimer of he eBook containing that text ", - "was not responding anymore, and at the same time, in projGut, ", - "searching for first aid no longer found that eBook as well. ", - "So here we are, with a perhaps much less interesting ", - "text for the test, but oh much much safer. ", + "Well, this is just some plain text we use for creating the ", + "test documents. It used to be a text from an online collection ", + "devoted to first aid, but if there was there an (online) lawyers ", + "first aid collection with legal advices, \"it\" might have quite ", + "probably advised one not to include \"it\"'s text or the text of ", + "any other online collection in one's code, unless one has money ", + "that one don't need and one is happy to donate for lawyers ", + "charity. Anyhow at some point, rechecking the usage of this text, ", + "it became uncertain that this text is free to use, because ", + "the web site in the disclaimer of he eBook containing that text ", + "was not responding anymore, and at the same time, in projGut, ", + "searching for first aid no longer found that eBook as well. ", + "So here we are, with a perhaps much less interesting ", + "text for the test, but oh much much safer. ", }; - - protected Directory dir; - protected Analyzer anlzr; - - /* @override constructor */ - public FunctionTestSetup(String name) { - super(name); - } - /* @override */ + protected Directory dir = null; + protected Analyzer anlzr = null; + @Override - protected void tearDown() throws Exception { + @After + public void tearDown() throws Exception { super.tearDown(); dir = null; anlzr = null; } - /* @override */ @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { super.setUp(); // prepare a small index with just a few documents. super.setUp(); dir = new RAMDirectory(); anlzr = new StandardAnalyzer(org.apache.lucene.util.Version.LUCENE_CURRENT); IndexWriter iw = new IndexWriter(dir, anlzr, - IndexWriter.MaxFieldLength.LIMITED); + IndexWriter.MaxFieldLength.LIMITED); // add docs not exactly in natural ID order, to verify we do check the order of docs by scores int remaining = N_DOCS; boolean done[] = new boolean[N_DOCS]; int i = 0; - while (remaining>0) { + while (remaining > 0) { if (done[i]) { - throw new Exception("to set this test correctly N_DOCS="+N_DOCS+" must be primary and greater than 2!"); + throw new Exception("to set this test correctly N_DOCS=" + N_DOCS + " must be primary and greater than 2!"); } - addDoc(iw,i); + addDoc(iw, i); done[i] = true; - i = (i+4)%N_DOCS; - remaining --; + i = (i + 4) % N_DOCS; + remaining--; } iw.close(); } @@ -110,36 +108,36 @@ private void addDoc(IndexWriter iw, int i) throws Exception { Document d = new Document(); Fieldable f; - int scoreAndID = i+1; - - f = new Field(ID_FIELD,id2String(scoreAndID),Field.Store.YES,Field.Index.NOT_ANALYZED); // for debug purposes + int scoreAndID = i + 1; + + f = new Field(ID_FIELD, id2String(scoreAndID), Field.Store.YES, Field.Index.NOT_ANALYZED); // for debug purposes f.setOmitNorms(true); d.add(f); - - f = new Field(TEXT_FIELD,"text of doc"+scoreAndID+textLine(i),Field.Store.NO,Field.Index.ANALYZED); // for regular search + + f = new Field(TEXT_FIELD, "text of doc" + scoreAndID + textLine(i), Field.Store.NO, Field.Index.ANALYZED); // for regular search f.setOmitNorms(true); d.add(f); - - f = new Field(INT_FIELD,""+scoreAndID,Field.Store.NO,Field.Index.NOT_ANALYZED); // for function scoring + + f = new Field(INT_FIELD, "" + scoreAndID, Field.Store.NO, Field.Index.NOT_ANALYZED); // for function scoring f.setOmitNorms(true); d.add(f); - - f = new Field(FLOAT_FIELD,scoreAndID+".000",Field.Store.NO,Field.Index.NOT_ANALYZED); // for function scoring + + f = new Field(FLOAT_FIELD, scoreAndID + ".000", Field.Store.NO, Field.Index.NOT_ANALYZED); // for function scoring f.setOmitNorms(true); d.add(f); iw.addDocument(d); - log("added: "+d); + log("added: " + d); } // 17 --> ID00017 protected String id2String(int scoreAndID) { - String s = "000000000"+scoreAndID; - int n = (""+N_DOCS).length() + 3; - int k = s.length() - n; - return "ID"+s.substring(k); + String s = "000000000" + scoreAndID; + int n = ("" + N_DOCS).length() + 3; + int k = s.length() - n; + return "ID" + s.substring(k); } - + // some text line for regular search private String textLine(int docNum) { return DOC_TEXT_LINES[docNum % DOC_TEXT_LINES.length]; @@ -147,11 +145,11 @@ // extract expected doc score from its ID Field: "ID7" --> 7.0 protected float expectedFieldScore(String docIDFieldVal) { - return Float.parseFloat(docIDFieldVal.substring(2)); + return Float.parseFloat(docIDFieldVal.substring(2)); } - + // debug messages (change DBG to true for anything to print) - protected void log (Object o) { + protected void log(Object o) { if (DBG) { System.out.println(o.toString()); } Index: src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java =================================================================== --- src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java (revision 836300) +++ src/test/org/apache/lucene/search/function/JustCompileSearchSpans.java (working copy) @@ -17,11 +17,11 @@ * limitations under the License. */ -import java.io.IOException; - import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.FieldCache; +import java.io.IOException; + /** * Holds all implementations of classes in the o.a.l.s.function package as a * back-compatibility test. It does not run any tests per-se, however if @@ -34,7 +34,6 @@ private static final String UNSUPPORTED_MSG = "unsupported: used for back-compat testing only !"; static final class JustCompileDocValues extends DocValues { - @Override public float floatVal(int doc) { throw new UnsupportedOperationException(UNSUPPORTED_MSG); @@ -44,7 +43,7 @@ public String toString(int doc) { throw new UnsupportedOperationException(UNSUPPORTED_MSG); } - + } static final class JustCompileFieldCacheSource extends FieldCacheSource { @@ -65,14 +64,13 @@ @Override public DocValues getCachedFieldValues(FieldCache cache, String field, - IndexReader reader) throws IOException { + IndexReader reader) throws IOException { throw new UnsupportedOperationException(UNSUPPORTED_MSG); } - + } static final class JustCompileValueSource extends ValueSource { - @Override public String description() { throw new UnsupportedOperationException(UNSUPPORTED_MSG); @@ -92,7 +90,7 @@ public int hashCode() { throw new UnsupportedOperationException(UNSUPPORTED_MSG); } - + } - + } Index: src/test/org/apache/lucene/search/function/TestCustomScoreQuery.java =================================================================== --- src/test/org/apache/lucene/search/function/TestCustomScoreQuery.java (revision 836300) +++ src/test/org/apache/lucene/search/function/TestCustomScoreQuery.java (working copy) @@ -17,99 +17,113 @@ * limitations under the License. */ +import org.apache.lucene.queryParser.QueryParser; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.*; +import org.apache.lucene.util.Version; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.util.HashMap; -import java.util.Iterator; -import org.apache.lucene.index.CorruptIndexException; -import org.apache.lucene.queryParser.QueryParser; -import org.apache.lucene.search.Explanation; -import org.apache.lucene.search.IndexSearcher; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.QueryUtils; -import org.apache.lucene.search.TopDocs; -import org.apache.lucene.util.Version; - /** * Test CustomScoreQuery search. */ +@SuppressWarnings({"MagicNumber"}) public class TestCustomScoreQuery extends FunctionTestSetup { - /* @override constructor */ - public TestCustomScoreQuery(String name) { - super(name); - } - /** Test that CustomScoreQuery of Type.BYTE returns the expected scores. */ - public void testCustomScoreByte () throws CorruptIndexException, Exception { + /** + * Test that CustomScoreQuery of Type.BYTE returns the expected scores. + */ + @Test + public void testCustomScoreByte() throws IOException, ParseException { // INT field values are small enough to be parsed as byte - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.BYTE,1.0); - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.BYTE,2.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.BYTE, 1.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.BYTE, 2.0); } - /** Test that CustomScoreQuery of Type.SHORT returns the expected scores. */ - public void testCustomScoreShort () throws CorruptIndexException, Exception { + /** + * Test that CustomScoreQuery of Type.SHORT returns the expected scores. + */ + @Test + public void testCustomScoreShort() throws IOException, ParseException { // INT field values are small enough to be parsed as short - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.SHORT,1.0); - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.SHORT,3.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.SHORT, 1.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.SHORT, 3.0); } - /** Test that CustomScoreQuery of Type.INT returns the expected scores. */ - public void testCustomScoreInt () throws CorruptIndexException, Exception { - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.INT,1.0); - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.INT,4.0); + /** + * Test that CustomScoreQuery of Type.INT returns the expected scores. + */ + @Test + public void testCustomScoreInt() throws IOException, ParseException { + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.INT, 1.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.INT, 4.0); } - /** Test that CustomScoreQuery of Type.FLOAT returns the expected scores. */ - public void testCustomScoreFloat () throws CorruptIndexException, Exception { + /** + * Test that CustomScoreQuery of Type.FLOAT returns the expected scores. + */ + @Test + public void testCustomScoreFloat() throws IOException, ParseException { // INT field can be parsed as float - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.FLOAT,1.0); - doTestCustomScore(INT_FIELD,FieldScoreQuery.Type.FLOAT,5.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.FLOAT, 1.0); + doTestCustomScore(INT_FIELD, FieldScoreQuery.Type.FLOAT, 5.0); // same values, but in flot format - doTestCustomScore(FLOAT_FIELD,FieldScoreQuery.Type.FLOAT,1.0); - doTestCustomScore(FLOAT_FIELD,FieldScoreQuery.Type.FLOAT,6.0); + doTestCustomScore(FLOAT_FIELD, FieldScoreQuery.Type.FLOAT, 1.0); + doTestCustomScore(FLOAT_FIELD, FieldScoreQuery.Type.FLOAT, 6.0); } // must have static class otherwise serialization tests fail + @SuppressWarnings({"SerializableHasSerializationMethods", "serial"}) private static class CustomAddQuery extends CustomScoreQuery { // constructor - CustomAddQuery (Query q, ValueSourceQuery qValSrc) { - super(q,qValSrc); + CustomAddQuery(Query q, ValueSourceQuery qValSrc) { + super(q, qValSrc); } + /*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#name() */ @Override public String name() { return "customAdd"; } + /*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#customScore(int, float, float) */ @Override public float customScore(int doc, float subQueryScore, float valSrcScore) { return subQueryScore + valSrcScore; } + /* (non-Javadoc)@see org.apache.lucene.search.function.CustomScoreQuery#customExplain(int, org.apache.lucene.search.Explanation, org.apache.lucene.search.Explanation)*/ @Override public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) { - float valSrcScore = valSrcExpl==null ? 0 : valSrcExpl.getValue(); - Explanation exp = new Explanation( valSrcScore + subQueryExpl.getValue(), "custom score: sum of:"); + float valSrcScore = valSrcExpl == null ? 0 : valSrcExpl.getValue(); + Explanation exp = new Explanation(valSrcScore + subQueryExpl.getValue(), "custom score: sum of:"); exp.addDetail(subQueryExpl); if (valSrcExpl != null) { exp.addDetail(valSrcExpl); } - return exp; - } + return exp; + } } - + // must have static class otherwise serialization tests fail + @SuppressWarnings({"SerializableHasSerializationMethods", "serial"}) private static class CustomMulAddQuery extends CustomScoreQuery { // constructor CustomMulAddQuery(Query q, ValueSourceQuery qValSrc1, ValueSourceQuery qValSrc2) { - super(q,new ValueSourceQuery[]{qValSrc1,qValSrc2}); + super(q, new ValueSourceQuery[]{qValSrc1, qValSrc2}); } + /*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#name() */ @Override public String name() { return "customMulAdd"; } + /*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#customScore(int, float, float) */ @Override public float customScore(int doc, float subQueryScore, float valSrcScores[]) { @@ -120,7 +134,8 @@ return subQueryScore + valSrcScores[0]; } return (subQueryScore + valSrcScores[0]) * valSrcScores[1]; // we know there are two - } + } + /* (non-Javadoc)@see org.apache.lucene.search.function.CustomScoreQuery#customExplain(int, org.apache.lucene.search.Explanation, org.apache.lucene.search.Explanation)*/ @Override public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) { @@ -137,121 +152,123 @@ Explanation exp2 = new Explanation(valSrcExpls[1].getValue() * exp.getValue(), "custom score: product of:"); exp2.addDetail(valSrcExpls[1]); exp2.addDetail(exp); - return exp2; - } + return exp2; + } } - + // Test that FieldScoreQuery returns docs with expected score. - private void doTestCustomScore (String field, FieldScoreQuery.Type tp, double dboost) throws CorruptIndexException, Exception { + private void doTestCustomScore(String field, FieldScoreQuery.Type tp, double dboost) throws IOException, ParseException { float boost = (float) dboost; IndexSearcher s = new IndexSearcher(dir, true); - FieldScoreQuery qValSrc = new FieldScoreQuery(field,tp); // a query that would score by the field - QueryParser qp = new QueryParser(Version.LUCENE_CURRENT, TEXT_FIELD,anlzr); + FieldScoreQuery qValSrc = new FieldScoreQuery(field, tp); // a query that would score by the field + QueryParser qp = new QueryParser(Version.LUCENE_CURRENT, TEXT_FIELD, anlzr); String qtxt = "first aid text"; // from the doc texts in FunctionQuerySetup. - + // regular (boolean) query. - Query q1 = qp.parse(qtxt); + Query q1 = qp.parse(qtxt); log(q1); - + // custom query, that should score the same as q1. - CustomScoreQuery q2CustomNeutral = new CustomScoreQuery(q1); + Query q2CustomNeutral = new CustomScoreQuery(q1); q2CustomNeutral.setBoost(boost); log(q2CustomNeutral); - + // custom query, that should (by default) multiply the scores of q1 by that of the field - CustomScoreQuery q3CustomMul = new CustomScoreQuery(q1,qValSrc); + CustomScoreQuery q3CustomMul = new CustomScoreQuery(q1, qValSrc); q3CustomMul.setStrict(true); q3CustomMul.setBoost(boost); log(q3CustomMul); - + // custom query, that should add the scores of q1 to that of the field - CustomScoreQuery q4CustomAdd = new CustomAddQuery(q1,qValSrc); + CustomScoreQuery q4CustomAdd = new CustomAddQuery(q1, qValSrc); q4CustomAdd.setStrict(true); q4CustomAdd.setBoost(boost); log(q4CustomAdd); // custom query, that multiplies and adds the field score to that of q1 - CustomScoreQuery q5CustomMulAdd = new CustomMulAddQuery(q1,qValSrc,qValSrc); + CustomScoreQuery q5CustomMulAdd = new CustomMulAddQuery(q1, qValSrc, qValSrc); q5CustomMulAdd.setStrict(true); q5CustomMulAdd.setBoost(boost); log(q5CustomMulAdd); // do al the searches - TopDocs td1 = s.search(q1,null,1000); - TopDocs td2CustomNeutral = s.search(q2CustomNeutral,null,1000); - TopDocs td3CustomMul = s.search(q3CustomMul,null,1000); - TopDocs td4CustomAdd = s.search(q4CustomAdd,null,1000); - TopDocs td5CustomMulAdd = s.search(q5CustomMulAdd,null,1000); - + TopDocs td1 = s.search(q1, null, 1000); + TopDocs td2CustomNeutral = s.search(q2CustomNeutral, null, 1000); + TopDocs td3CustomMul = s.search(q3CustomMul, null, 1000); + TopDocs td4CustomAdd = s.search(q4CustomAdd, null, 1000); + TopDocs td5CustomMulAdd = s.search(q5CustomMulAdd, null, 1000); + // put results in map so we can verify the scores although they have changed - HashMap h1 = topDocsToMap(td1); - HashMap h2CustomNeutral = topDocsToMap(td2CustomNeutral); - HashMap h3CustomMul = topDocsToMap(td3CustomMul); - HashMap h4CustomAdd = topDocsToMap(td4CustomAdd); - HashMap h5CustomMulAdd = topDocsToMap(td5CustomMulAdd); - - verifyResults(boost, s, - h1, h2CustomNeutral, h3CustomMul, h4CustomAdd, h5CustomMulAdd, - q1, q2CustomNeutral, q3CustomMul, q4CustomAdd, q5CustomMulAdd); + HashMap h1 = topDocsToMap(td1); + HashMap h2CustomNeutral = topDocsToMap(td2CustomNeutral); + HashMap h3CustomMul = topDocsToMap(td3CustomMul); + HashMap h4CustomAdd = topDocsToMap(td4CustomAdd); + HashMap h5CustomMulAdd = topDocsToMap(td5CustomMulAdd); + + verifyResults(boost, s, + h1, h2CustomNeutral, h3CustomMul, h4CustomAdd, h5CustomMulAdd, + q1, q2CustomNeutral, q3CustomMul, q4CustomAdd, q5CustomMulAdd); } - + // verify results are as expected. - private void verifyResults(float boost, IndexSearcher s, - HashMap h1, HashMap h2customNeutral, HashMap h3CustomMul, HashMap h4CustomAdd, HashMap h5CustomMulAdd, - Query q1, Query q2, Query q3, Query q4, Query q5) throws Exception { - + private void verifyResults(float boost, IndexSearcher s, + HashMap h1, HashMap h2customNeutral, + HashMap h3CustomMul, HashMap h4CustomAdd, + HashMap h5CustomMulAdd, + Query q1, Query q2, Query q3, Query q4, Query q5) throws IOException { + // verify numbers of matches - log("#hits = "+h1.size()); - assertEquals("queries should have same #hits",h1.size(),h2customNeutral.size()); - assertEquals("queries should have same #hits",h1.size(),h3CustomMul.size()); - assertEquals("queries should have same #hits",h1.size(),h4CustomAdd.size()); - assertEquals("queries should have same #hits",h1.size(),h5CustomMulAdd.size()); - + log("#hits = " + h1.size()); + assertEquals("queries should have same #hits", h1.size(), h2customNeutral.size()); + assertEquals("queries should have same #hits", h1.size(), h3CustomMul.size()); + assertEquals("queries should have same #hits", h1.size(), h4CustomAdd.size()); + assertEquals("queries should have same #hits", h1.size(), h5CustomMulAdd.size()); + + // verify scores ratios - for (Iterator it = h1.keySet().iterator(); it.hasNext();) { - Integer x = (Integer) it.next(); + for (int x : h1.keySet()) { - int doc = x.intValue(); - log("doc = "+doc); + log("doc = " + x); - float fieldScore = expectedFieldScore(s.getIndexReader().document(doc).get(ID_FIELD)); - log("fieldScore = "+fieldScore); - assertTrue("fieldScore should not be 0",fieldScore>0); + float fieldScore = expectedFieldScore(s.getIndexReader().document(x).get(ID_FIELD)); + log("fieldScore = " + fieldScore); + assertTrue("fieldScore should not be 0", fieldScore > 0); - float score1 = ((Float)h1.get(x)).floatValue(); - logResult("score1=", s, q1, doc, score1); - - float score2 = ((Float)h2customNeutral.get(x)).floatValue(); - logResult("score2=", s, q2, doc, score2); + float score1 = h1.get(x); + logResult("score1=", s, q1, x, score1); + + float score2 = h2customNeutral.get(x); + logResult("score2=", s, q2, x, score2); assertEquals("same score (just boosted) for neutral", boost * score1, score2, TEST_SCORE_TOLERANCE_DELTA); - float score3 = ((Float)h3CustomMul.get(x)).floatValue(); - logResult("score3=", s, q3, doc, score3); + float score3 = h3CustomMul.get(x); + logResult("score3=", s, q3, x, score3); assertEquals("new score for custom mul", boost * fieldScore * score1, score3, TEST_SCORE_TOLERANCE_DELTA); - - float score4 = ((Float)h4CustomAdd.get(x)).floatValue(); - logResult("score4=", s, q4, doc, score4); + + float score4 = h4CustomAdd.get(x); + logResult("score4=", s, q4, x, score4); assertEquals("new score for custom add", boost * (fieldScore + score1), score4, TEST_SCORE_TOLERANCE_DELTA); - - float score5 = ((Float)h5CustomMulAdd.get(x)).floatValue(); - logResult("score5=", s, q5, doc, score5); + + float score5 = h5CustomMulAdd.get(x); + logResult("score5=", s, q5, x, score5); assertEquals("new score for custom mul add", boost * fieldScore * (score1 + fieldScore), score5, TEST_SCORE_TOLERANCE_DELTA); } } - private void logResult(String msg, IndexSearcher s, Query q, int doc, float score1) throws IOException { - QueryUtils.check(q,s); - log(msg+" "+score1); - log("Explain by: "+q); - log(s.explain(q,doc)); + + private void logResult(String msg, Searcher s, Query q, int doc, float score1) throws IOException { + QueryUtils.check(q, s); + log(msg + " " + score1); + log("Explain by: " + q); + log(s.explain(q, doc)); } // since custom scoring modifies the order of docs, map results // by doc ids so that we can later compare/verify them - private HashMap topDocsToMap(TopDocs td) { - HashMap h = new HashMap(); - for (int i=0; i topDocsToMap(TopDocs td) { + HashMap h = new HashMap(); + for (int i = 0; i < td.totalHits; i++) { + h.put(td.scoreDocs[i].doc, td.scoreDocs[i].score); } return h; } Index: src/test/org/apache/lucene/util/LuceneTestCaseJ4.java =================================================================== --- src/test/org/apache/lucene/util/LuceneTestCaseJ4.java (revision 0) +++ src/test/org/apache/lucene/util/LuceneTestCaseJ4.java (revision 0) @@ -0,0 +1,254 @@ +package org.apache.lucene.util; + +/** + * 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 org.apache.lucene.index.ConcurrentMergeScheduler; +import org.apache.lucene.search.FieldCache; +import org.apache.lucene.search.FieldCache.CacheEntry; +import org.apache.lucene.util.FieldCacheSanityChecker.Insanity; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestWatchman; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Base class for all Lucene unit tests, Junit4 variant. + * Replaces LuceneTestCase. + *

+ *

+ *

+ * If you + * override either setUp() or + * tearDown() in your unit test, make sure you + * call super.setUp() and + * super.tearDown() + *

+ * + * @After - replaces setup + * @Before - replaces teardown + * @Test - any public method with this annotation is a test case, regardless + * of its name + *

+ *

+ * See Junit4 documentation for a complete list of features at + * http://junit.org/junit/javadoc/4.7/ + *

+ * Import from org.junit rather than junit.framework. + *

+ * You should be able to use this class anywhere you used LuceneTestCase + * if you annotate your derived class correctly with the annotations above + * @see assertSaneFieldCaches + *

+ */ + + +// If we really need functionality in runBare override from LuceneTestCase, +// we can introduce RunBareWrapper and override runChild, and add the +// @RunWith annotation as below. runChild will be called for +// every test. But the functionality we used to +// get from that override is provided by InterceptTestCaseEvents +//@RunWith(RunBareWrapper.class) +public class LuceneTestCaseJ4 extends TestWatchman { + + // This is how we get control when errors occur. + // Think of this as start/end/success/failed + // events. + @Rule + public InterceptTestCaseEvents intercept = new InterceptTestCaseEvents(this); + + public LuceneTestCaseJ4() { + } + + public LuceneTestCaseJ4(String name) { + this.name = name; + } + + @Before + public void setUp() throws Exception { + ConcurrentMergeScheduler.setTestMode(); + seed = null; + } + + + /** + * Forcible purges all cache entries from the FieldCache. + *

+ * This method will be called by tearDown to clean up FieldCache.DEFAULT. + * If a (poorly written) test has some expectation that the FieldCache + * will persist across test methods (ie: a static IndexReader) this + * method can be overridden to do nothing. + *

+ * + * @see FieldCache#purgeAllCaches() + */ + protected void purgeFieldCache(final FieldCache fc) { + fc.purgeAllCaches(); + } + + protected String getTestLabel() { + return getClass().getName() + "." + getName(); + } + + @After + public void tearDown() throws Exception { + try { + // this isn't as useful as calling directly from the scope where the + // index readers are used, because they could be gc'ed just before + // tearDown is called. + // But it's better then nothing. + assertSaneFieldCaches(getTestLabel()); + + if (ConcurrentMergeScheduler.anyUnhandledExceptions()) { + // Clear the failure so that we don't just keep + // failing subsequent test cases + ConcurrentMergeScheduler.clearUnhandledExceptions(); + fail("ConcurrentMergeScheduler hit unhandled exceptions"); + } + } finally { + purgeFieldCache(FieldCache.DEFAULT); + } + } + + /** + * Asserts that FieldCacheSanityChecker does not detect any + * problems with FieldCache.DEFAULT. + *

+ * If any problems are found, they are logged to System.err + * (allong with the msg) when the Assertion is thrown. + *

+ *

+ * This method is called by tearDown after every test method, + * however IndexReaders scoped inside test methods may be garbage + * collected prior to this method being called, causing errors to + * be overlooked. Tests are encouraged to keep their IndexReaders + * scoped at the class level, or to explicitly call this method + * directly in the same scope as the IndexReader. + *

+ * + * @see FieldCacheSanityChecker + */ + protected void assertSaneFieldCaches(final String msg) { + final CacheEntry[] entries = FieldCache.DEFAULT.getCacheEntries(); + Insanity[] insanity = null; + try { + try { + insanity = FieldCacheSanityChecker.checkSanity(entries); + } catch (RuntimeException e) { + dumpArray(msg + ": FieldCache", entries, System.err); + throw e; + } + + assertEquals(msg + ": Insane FieldCache usage(s) found", + 0, insanity.length); + insanity = null; + } finally { + + // report this in the event of any exception/failure + // if no failure, then insanity will be null anyway + if (null != insanity) { + dumpArray(msg + ": Insane FieldCache usage(s)", insanity, System.err); + } + + } + } + + /** + * Convinience method for logging an iterator. + * + * @param label String logged before/after the items in the iterator + * @param iter Each next() is toString()ed and logged on it's own line. If iter is null this is logged differnetly then an empty iterator. + * @param stream Stream to log messages to. + */ + public static void dumpIterator(String label, Iterator iter, + PrintStream stream) { + stream.println("*** BEGIN " + label + " ***"); + if (null == iter) { + stream.println(" ... NULL ..."); + } else { + while (iter.hasNext()) { + stream.println(iter.next().toString()); + } + } + stream.println("*** END " + label + " ***"); + } + + /** + * Convinience method for logging an array. Wraps the array in an iterator and delegates + * + * @see dumpIterator(String,Iterator,PrintStream) + */ + public static void dumpArray(String label, Object[] objs, + PrintStream stream) { + Iterator iter = (null == objs) ? null : Arrays.asList(objs).iterator(); + dumpIterator(label, iter, stream); + } + + /** + * Returns a {@link Random} instance for generating random numbers during the test. + * The random seed is logged during test execution and printed to System.out on any failure + * for reproducing the test using {@link #newRandom(long)} with the recorded seed + * . + */ + public Random newRandom() { + if (seed != null) { + throw new IllegalStateException("please call LuceneTestCase.newRandom only once per test"); + } + return newRandom(seedRnd.nextLong()); + } + + /** + * Returns a {@link Random} instance for generating random numbers during the test. + * If an error occurs in the test that is not reproducible, you can use this method to + * initialize the number generator with the seed that was printed out during the failing test. + */ + public Random newRandom(long seed) { + if (this.seed != null) { + throw new IllegalStateException("please call LuceneTestCase.newRandom only once per test"); + } + this.seed = Long.valueOf(seed); + return new Random(seed); + } + + public String getName() { + return this.name; + } + + // We get here fro InterceptTestCaseEvents on the 'failed' event.... + public void reportAdditionalFailureInfo() { + if (seed != null) { + System.out.println("NOTE: random seed of testcase '" + getName() + "' was: " + seed); + } + } + + // recorded seed + protected Long seed = null; + + // static members + private static final Random seedRnd = new Random(); + + private String name = ""; + +} Index: src/test/org/apache/lucene/util/LuceneTestCase.java =================================================================== --- src/test/org/apache/lucene/util/LuceneTestCase.java (revision 836280) +++ src/test/org/apache/lucene/util/LuceneTestCase.java (working copy) @@ -47,7 +47,11 @@ * super.tearDown() *

* @see #assertSaneFieldCaches + * + * @deprecated Replaced by {@link #LuceneTestCaseJ4} + * */ +@Deprecated public abstract class LuceneTestCase extends TestCase { public LuceneTestCase() { Index: src/test/org/apache/lucene/util/InterceptTestCaseEvents.java =================================================================== --- src/test/org/apache/lucene/util/InterceptTestCaseEvents.java (revision 0) +++ src/test/org/apache/lucene/util/InterceptTestCaseEvents.java (revision 0) @@ -0,0 +1,60 @@ +package org.apache.lucene.util; + +/** + * 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 org.junit.rules.TestWatchman; +import org.junit.runners.model.FrameworkMethod; + +import java.lang.reflect.Method; + + +public final class InterceptTestCaseEvents extends TestWatchman { + private Object obj; + + public InterceptTestCaseEvents(Object obj) { + this.obj = obj; + } + + @Override + public void failed(Throwable e, FrameworkMethod method) { + try { + Method reporter = method.getMethod().getDeclaringClass().getMethod("reportAdditionalFailureInfo",(Class[]) null); + reporter.invoke(obj, (Object[])null); + } catch (Exception e1) { + System.err.println("InterceptTestCaseEvents.failed(). Cannot invoke reportAdditionalFailureInfo() method in" + + " consuming class, is it declared and public?"); + } + super.failed(e, method); + } + + @Override + public void finished(FrameworkMethod method) { + super.finished(method); + } + + @Override + public void starting(FrameworkMethod method) { + super.starting(method); + } + + @Override + public void succeeded(FrameworkMethod method) { + super.succeeded(method); + } +}