Index: src/test/org/apache/lucene/util/LuceneTestCaseJ4.java =================================================================== --- src/test/org/apache/lucene/util/LuceneTestCaseJ4.java Sun Nov 29 11:57:20 EST 2009 +++ src/test/org/apache/lucene/util/LuceneTestCaseJ4.java Sun Nov 29 11:57:20 EST 2009 @@ -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()
+ *
+ * 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: CHANGES.txt =================================================================== --- CHANGES.txt (revision 885214) +++ CHANGES.txt Sun Nov 29 12:44:41 EST 2009 @@ -47,6 +47,8 @@ Test Cases +* LUCENE-2037 Allow Junit4 tests in our envrionment (Erick Erickson) + * LUCENE-1844: Speed up the unit tests (Mark Miller, Erick Erickson, Mike McCandless) Index: src/test/org/apache/lucene/util/InterceptTestCaseEvents.java =================================================================== --- src/test/org/apache/lucene/util/InterceptTestCaseEvents.java Sun Nov 29 11:57:21 EST 2009 +++ src/test/org/apache/lucene/util/InterceptTestCaseEvents.java Sun Nov 29 11:57:21 EST 2009 @@ -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); + } +} Index: src/test/org/apache/lucene/search/function/FunctionTestSetup.java =================================================================== --- src/test/org/apache/lucene/search/function/FunctionTestSetup.java (revision 832972) +++ src/test/org/apache/lucene/search/function/FunctionTestSetup.java Sun Nov 29 11:55:29 EST 2009 @@ -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; + protected Directory dir = null; + protected Analyzer anlzr = null; - + - /* @override constructor */ - public FunctionTestSetup(String name) { - super(name); - } - - /* @override */ @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; + i = (i + 4) % N_DOCS; - remaining --; + 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; + int scoreAndID = i + 1; - + - f = new Field(ID_FIELD,id2String(scoreAndID),Field.Store.YES,Field.Index.NOT_ANALYZED); // for debug purposes + 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; + String s = "000000000" + scoreAndID; - int n = (""+N_DOCS).length() + 3; + int n = ("" + N_DOCS).length() + 3; - int k = s.length() - n; + int k = s.length() - n; - return "ID"+s.substring(k); + 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: common-build.xml =================================================================== --- common-build.xml (revision 884136) +++ common-build.xml Sun Nov 29 11:59:36 EST 2009 @@ -47,7 +47,7 @@+ *
* 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 OrdFieldSource + */ + @Test - public void testOrdFieldRank () throws CorruptIndexException, Exception { + public void testOrdFieldRank() throws CorruptIndexException, Exception { - doTestRank(ID_FIELD,true); + doTestRank(ID_FIELD, true); } - /** Test ReverseOrdFieldSource */ + /** + * Test ReverseOrdFieldSource + */ + @Test - public void testReverseOrdFieldRank () throws CorruptIndexException, Exception { + public void testReverseOrdFieldRank() throws CorruptIndexException, Exception { - doTestRank(ID_FIELD,false); + 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); + log("test: " + q); - QueryUtils.check(q,s); + 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.) - + ? "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; isuper.tearDown()
*
* @see #assertSaneFieldCaches
+ *
+ * @deprecated Replaced by {@link #LuceneTestCaseJ4}
+ *
*/
+@Deprecated
public abstract class LuceneTestCase extends TestCase {
public LuceneTestCase() {
Index: src/test/org/apache/lucene/search/function/TestFieldScoreQuery.java
===================================================================
--- src/test/org/apache/lucene/search/function/TestFieldScoreQuery.java (revision 821340)
+++ src/test/org/apache/lucene/search/function/TestFieldScoreQuery.java Sun Nov 29 11:55:29 EST 2009
@@ -18,14 +18,16 @@
*/
import java.util.HashMap;
+import java.util.Map;
-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.junit.Test;
+import static org.junit.Assert.*;
/**
* Test FieldScoreQuery search.
@@ -38,32 +40,32 @@
*
* 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