1 package org.apache.lucene.util;
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
21 import java.io.IOException;
22 import java.io.PrintStream;
23 import java.lang.annotation.Documented;
24 import java.lang.annotation.Inherited;
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 import java.lang.reflect.Constructor;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
31 import java.util.Map.Entry;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.TimeUnit;
35 import org.apache.lucene.analysis.Analyzer;
36 import org.apache.lucene.document.Field.Index;
37 import org.apache.lucene.document.Field.Store;
38 import org.apache.lucene.document.Field.TermVector;
39 import org.apache.lucene.document.Field;
40 import org.apache.lucene.index.IndexReader;
41 import org.apache.lucene.index.IndexWriterConfig;
42 import org.apache.lucene.index.LogByteSizeMergePolicy;
43 import org.apache.lucene.index.LogDocMergePolicy;
44 import org.apache.lucene.index.LogMergePolicy;
45 import org.apache.lucene.index.MergePolicy;
46 import org.apache.lucene.index.MockRandomMergePolicy;
47 import org.apache.lucene.index.SerialMergeScheduler;
48 import org.apache.lucene.index.SlowMultiReaderWrapper;
49 import org.apache.lucene.index.TieredMergePolicy;
50 import org.apache.lucene.search.BooleanQuery;
51 import org.apache.lucene.search.FieldCache.CacheEntry;
52 import org.apache.lucene.search.AssertingIndexSearcher;
53 import org.apache.lucene.search.FieldCache;
54 import org.apache.lucene.search.IndexSearcher;
55 import org.apache.lucene.store.Directory;
56 import org.apache.lucene.store.FSDirectory;
57 import org.apache.lucene.store.LockFactory;
58 import org.apache.lucene.store.MockDirectoryWrapper;
59 import org.apache.lucene.util.FieldCacheSanityChecker.Insanity;
60 import org.junit.After;
61 import org.junit.AfterClass;
62 import org.junit.Assert;
63 import org.junit.Assume;
64 import org.junit.Before;
65 import org.junit.BeforeClass;
66 import org.junit.Ignore;
67 import org.junit.Rule;
68 import org.junit.Test;
69 import org.junit.rules.TestWatchman;
70 import org.junit.runner.Description;
71 import org.junit.runner.RunWith;
72 import org.junit.runner.manipulation.Filter;
73 import org.junit.runner.manipulation.NoTestsRemainException;
74 import org.junit.runner.notification.Failure;
75 import org.junit.runner.notification.RunListener;
76 import org.junit.runner.notification.RunNotifier;
77 import org.junit.runners.BlockJUnit4ClassRunner;
78 import org.junit.runners.model.FrameworkMethod;
79 import org.junit.runners.model.InitializationError;
82 * Base class for all Lucene unit tests, Junit3 or Junit4 variant.
87 * override either <code>setUp()</code> or
88 * <code>tearDown()</code> in your unit test, make sure you
89 * call <code>super.setUp()</code> and
90 * <code>super.tearDown()</code>
93 * <code>@After</code> - replaces setup
94 * <code>@Before</code> - replaces teardown
95 * <code>@Test</code> - any public method with this annotation is a test case, regardless
99 * See Junit4 <a href="http://junit.org/junit/javadoc/4.7/">documentation</a> for a complete list of features.
101 * Import from org.junit rather than junit.framework.
103 * You should be able to use this class anywhere you used LuceneTestCase
104 * if you annotate your derived class correctly with the annotations above
105 * @see #assertSaneFieldCaches(String)
108 @RunWith(LuceneTestCase.LuceneTestCaseRunner.class)
109 public abstract class LuceneTestCase extends Assert {
112 * true iff tests are run in verbose mode. Note: if it is false, tests are not
113 * expected to print any messages.
115 public static final boolean VERBOSE = Boolean.getBoolean("tests.verbose");
117 /** Use this constant when creating Analyzers and any other version-dependent stuff.
118 * <p><b>NOTE:</b> Change this when development starts for new Lucene version:
120 public static final Version TEST_VERSION_CURRENT = Version.LUCENE_33;
123 * If this is set, it is the only method that should run.
125 static final String TEST_METHOD;
127 /** Create indexes in this directory, optimally use a subdir, named after the test */
128 public static final File TEMP_DIR;
130 String method = System.getProperty("testmethod", "").trim();
131 TEST_METHOD = method.length() == 0 ? null : method;
132 String s = System.getProperty("tempDir", System.getProperty("java.io.tmpdir"));
134 throw new RuntimeException("To run tests, you need to define system property 'tempDir' or 'java.io.tmpdir'.");
135 TEMP_DIR = new File(s);
139 /** set of directories we created, in afterclass we try to clean these up */
140 private static final Map<File, StackTraceElement[]> tempDirs = Collections.synchronizedMap(new HashMap<File, StackTraceElement[]>());
142 // by default we randomly pick a different codec for
143 // each test case (non-J4 tests) and each test class (J4
145 /** Gets the locale to run tests with */
146 public static final String TEST_LOCALE = System.getProperty("tests.locale", "random");
147 /** Gets the timezone to run tests with */
148 public static final String TEST_TIMEZONE = System.getProperty("tests.timezone", "random");
149 /** Gets the directory to run tests with */
150 public static final String TEST_DIRECTORY = System.getProperty("tests.directory", "random");
151 /** Get the number of times to run tests */
152 public static final int TEST_ITER = Integer.parseInt(System.getProperty("tests.iter", "1"));
153 /** Get the minimum number of times to run tests until a failure happens */
154 public static final int TEST_ITER_MIN = Integer.parseInt(System.getProperty("tests.iter.min", Integer.toString(TEST_ITER)));
155 /** Get the random seed for tests */
156 public static final String TEST_SEED = System.getProperty("tests.seed", "random");
157 /** whether or not nightly tests should run */
158 public static final boolean TEST_NIGHTLY = Boolean.parseBoolean(System.getProperty("tests.nightly", "false"));
159 /** the line file used by LineFileDocs */
160 public static final String TEST_LINE_DOCS_FILE = System.getProperty("tests.linedocsfile", "europarl.lines.txt.gz");
161 /** whether or not to clean threads between test invocations: "false", "perMethod", "perClass" */
162 public static final String TEST_CLEAN_THREADS = System.getProperty("tests.cleanthreads", "perClass");
165 * A random multiplier which you should use when writing random tests:
166 * multiply it by the number of iterations
168 public static final int RANDOM_MULTIPLIER = Integer.parseInt(System.getProperty("tests.multiplier", "1"));
170 private int savedBoolMaxClauseCount;
172 private volatile Thread.UncaughtExceptionHandler savedUncaughtExceptionHandler = null;
174 /** Used to track if setUp and tearDown are called correctly from subclasses */
175 private static State state = State.INITIAL;
177 private static enum State {
178 INITIAL, // no tests ran yet
179 SETUP, // test has called setUp()
180 RANTEST, // test is running
181 TEARDOWN // test has called tearDown()
184 private static class UncaughtExceptionEntry {
185 public final Thread thread;
186 public final Throwable exception;
188 public UncaughtExceptionEntry(Thread thread, Throwable exception) {
189 this.thread = thread;
190 this.exception = exception;
193 private List<UncaughtExceptionEntry> uncaughtExceptions = Collections.synchronizedList(new ArrayList<UncaughtExceptionEntry>());
195 private static Locale locale;
196 private static Locale savedLocale;
197 private static TimeZone timeZone;
198 private static TimeZone savedTimeZone;
200 protected static Map<MockDirectoryWrapper,StackTraceElement[]> stores;
202 private static class TwoLongs {
203 public final long l1, l2;
205 public TwoLongs(long l1, long l2) {
211 public String toString() {
212 return l1 + ":" + l2;
215 public static TwoLongs fromString(String s) {
216 final int i = s.indexOf(':');
218 return new TwoLongs(Long.parseLong(s.substring(0, i)),
219 Long.parseLong(s.substring(1+i)));
223 /** @deprecated: until we fix no-fork problems in solr tests */
225 private static List<String> testClassesRun = new ArrayList<String>();
228 public static void beforeClassLuceneTestCaseJ4() {
229 state = State.INITIAL;
230 staticSeed = "random".equals(TEST_SEED) ? seedRand.nextLong() : TwoLongs.fromString(TEST_SEED).l1;
231 random.setSeed(staticSeed);
233 stores = Collections.synchronizedMap(new IdentityHashMap<MockDirectoryWrapper,StackTraceElement[]>());
234 // enable this by default, for IDE consistency with ant tests (as its the default from ant)
235 // TODO: really should be in solr base classes, but some extend LTC directly.
236 // we do this in beforeClass, because some tests currently disable it
237 if (System.getProperty("solr.directoryFactory") == null) {
238 System.setProperty("solr.directoryFactory", "org.apache.solr.core.MockDirectoryFactory");
240 // this code consumes randoms where 4.0's lucenetestcase would: to make seeds work across both branches.
241 // TODO: doesn't completely work, because what if we get mockrandom codec?!
242 if (random.nextInt(4) != 0) {
243 random.nextInt(); // consume RandomCodecProvider's seed.
245 // end compatibility random-consumption
246 savedLocale = Locale.getDefault();
247 locale = TEST_LOCALE.equals("random") ? randomLocale(random) : localeForName(TEST_LOCALE);
248 Locale.setDefault(locale);
249 savedTimeZone = TimeZone.getDefault();
250 timeZone = TEST_TIMEZONE.equals("random") ? randomTimeZone(random) : TimeZone.getTimeZone(TEST_TIMEZONE);
251 TimeZone.setDefault(timeZone);
256 public static void afterClassLuceneTestCaseJ4() {
258 assertTrue("ensure your setUp() calls super.setUp() and your tearDown() calls super.tearDown()!!!",
259 state == State.INITIAL || state == State.TEARDOWN);
261 state = State.INITIAL;
262 if (! "false".equals(TEST_CLEAN_THREADS)) {
263 int rogueThreads = threadCleanup("test class");
264 if (rogueThreads > 0) {
265 // TODO: fail here once the leaks are fixed.
266 System.err.println("RESOURCE LEAK: test class left " + rogueThreads + " thread(s) running");
269 Locale.setDefault(savedLocale);
270 TimeZone.setDefault(savedTimeZone);
271 System.clearProperty("solr.solr.home");
272 System.clearProperty("solr.data.dir");
273 // now look for unclosed resources
275 for (MockDirectoryWrapper d : stores.keySet()) {
277 StackTraceElement elements[] = stores.get(d);
278 // Look for the first class that is not LuceneTestCase that requested
279 // a Directory. The first two items are of Thread's, so skipping over
281 StackTraceElement element = null;
282 for (int i = 2; i < elements.length; i++) {
283 StackTraceElement ste = elements[i];
284 if (ste.getClassName().indexOf("LuceneTestCase") == -1) {
289 fail("directory of test was not closed, opened from: " + element);
293 // if verbose or tests failed, report some information back
294 if (VERBOSE || testsFailed)
295 System.err.println("NOTE: test params are: " +
297 ", timezone=" + (timeZone == null ? "(null)" : timeZone.getID()));
299 System.err.println("NOTE: all tests run in this JVM:");
300 System.err.println(Arrays.toString(testClassesRun.toArray()));
301 System.err.println("NOTE: " + System.getProperty("os.name") + " "
302 + System.getProperty("os.version") + " "
303 + System.getProperty("os.arch") + "/"
304 + System.getProperty("java.vendor") + " "
305 + System.getProperty("java.version") + " "
306 + (Constants.JRE_IS_64BIT ? "(64-bit)" : "(32-bit)") + "/"
307 + "cpus=" + Runtime.getRuntime().availableProcessors() + ","
308 + "threads=" + Thread.activeCount() + ","
309 + "free=" + Runtime.getRuntime().freeMemory() + ","
310 + "total=" + Runtime.getRuntime().totalMemory());
312 // clear out any temp directories if we can
314 for (Entry<File, StackTraceElement[]> entry : tempDirs.entrySet()) {
316 _TestUtil.rmDir(entry.getKey());
317 } catch (IOException e) {
319 System.err.println("path " + entry.getKey() + " allocated from");
320 // first two STE's are Java's
321 StackTraceElement[] elements = entry.getValue();
322 for (int i = 2; i < elements.length; i++) {
323 StackTraceElement ste = elements[i];
324 // print only our code's stack information
325 if (ste.getClassName().indexOf("org.apache.lucene") == -1) break;
326 System.err.println("\t" + ste);
328 fail("could not remove temp dir: " + entry.getKey());
334 private static boolean testsFailed; /* true if any tests failed */
336 // This is how we get control when errors occur.
337 // Think of this as start/end/success/failed
340 public final TestWatchman intercept = new TestWatchman() {
343 public void failed(Throwable e, FrameworkMethod method) {
344 // org.junit.internal.AssumptionViolatedException in older releases
345 // org.junit.Assume.AssumptionViolatedException in recent ones
346 if (e.getClass().getName().endsWith("AssumptionViolatedException")) {
347 if (e.getCause() instanceof TestIgnoredException)
349 System.err.print("NOTE: Assume failed in '" + method.getName() + "' (ignored):");
351 System.err.println();
352 e.printStackTrace(System.err);
354 System.err.print(" ");
355 System.err.println(e.getMessage());
359 reportAdditionalFailureInfo();
361 super.failed(e, method);
365 public void starting(FrameworkMethod method) {
366 // set current method name for logging
367 LuceneTestCase.this.name = method.getName();
369 assertTrue("ensure your setUp() calls super.setUp()!!!", state == State.SETUP);
371 state = State.RANTEST;
372 super.starting(method);
377 public void setUp() throws Exception {
378 seed = "random".equals(TEST_SEED) ? seedRand.nextLong() : TwoLongs.fromString(TEST_SEED).l2;
379 random.setSeed(seed);
381 assertTrue("ensure your tearDown() calls super.tearDown()!!!", (state == State.INITIAL || state == State.TEARDOWN));
384 savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
385 Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
386 public void uncaughtException(Thread t, Throwable e) {
388 uncaughtExceptions.add(new UncaughtExceptionEntry(t, e));
389 if (savedUncaughtExceptionHandler != null)
390 savedUncaughtExceptionHandler.uncaughtException(t, e);
394 savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount();
399 * Forcible purges all cache entries from the FieldCache.
401 * This method will be called by tearDown to clean up FieldCache.DEFAULT.
402 * If a (poorly written) test has some expectation that the FieldCache
403 * will persist across test methods (ie: a static IndexReader) this
404 * method can be overridden to do nothing.
407 * @see FieldCache#purgeAllCaches()
409 protected void purgeFieldCache(final FieldCache fc) {
413 protected String getTestLabel() {
414 return getClass().getName() + "." + getName();
417 public static void setUseCompoundFile(MergePolicy mp, boolean useCompound) {
418 if (mp instanceof LogMergePolicy) {
419 ((LogMergePolicy) mp).setUseCompoundFile(useCompound);
420 } else if (mp instanceof TieredMergePolicy) {
421 ((TieredMergePolicy) mp).setUseCompoundFile(useCompound);
423 fail("MergePolicy (compound-file) not supported " + mp);
427 public static void setMergeFactor(MergePolicy mp, int mergeFactor) {
428 if (mp instanceof LogMergePolicy) {
429 ((LogMergePolicy) mp).setMergeFactor(mergeFactor);
430 } else if (mp instanceof TieredMergePolicy) {
431 ((TieredMergePolicy) mp).setMaxMergeAtOnce(mergeFactor);
432 ((TieredMergePolicy) mp).setMaxMergeAtOnceExplicit(mergeFactor);
434 fail("MergePolicy not supported " + mp);
439 public void tearDown() throws Exception {
441 // Note: we allow a test to go straight from SETUP -> TEARDOWN (without ever entering the RANTEST state)
442 // because if you assume() inside setUp(), it skips the test and the TestWatchman has no way to know...
443 assertTrue("ensure your setUp() calls super.setUp()!!!", state == State.RANTEST || state == State.SETUP);
445 state = State.TEARDOWN;
446 BooleanQuery.setMaxClauseCount(savedBoolMaxClauseCount);
447 if ("perMethod".equals(TEST_CLEAN_THREADS)) {
448 int rogueThreads = threadCleanup("test method: '" + getName() + "'");
449 if (rogueThreads > 0) {
450 System.err.println("RESOURCE LEAK: test method: '" + getName()
451 + "' left " + rogueThreads + " thread(s) running");
452 // TODO: fail, but print seed for now.
453 if (!testsFailed && uncaughtExceptions.isEmpty()) {
454 reportAdditionalFailureInfo();
458 Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler);
461 if (!uncaughtExceptions.isEmpty()) {
463 reportAdditionalFailureInfo();
464 System.err.println("The following exceptions were thrown by threads:");
465 for (UncaughtExceptionEntry entry : uncaughtExceptions) {
466 System.err.println("*** Thread: " + entry.thread.getName() + " ***");
467 entry.exception.printStackTrace(System.err);
469 fail("Some threads threw uncaught exceptions!");
472 // calling assertSaneFieldCaches here isn't as useful as having test
473 // classes call it directly from the scope where the index readers
474 // are used, because they could be gc'ed just before this tearDown
477 // But it's better then nothing.
479 // If you are testing functionality that you know for a fact
480 // "violates" FieldCache sanity, then you should either explicitly
481 // call purgeFieldCache at the end of your test method, or refactor
482 // your Test class so that the inconsistant FieldCache usages are
483 // isolated in distinct test methods
484 assertSaneFieldCaches(getTestLabel());
487 purgeFieldCache(FieldCache.DEFAULT);
491 private final static int THREAD_STOP_GRACE_MSEC = 50;
492 // jvm-wide list of 'rogue threads' we found, so they only get reported once.
493 private final static IdentityHashMap<Thread,Boolean> rogueThreads = new IdentityHashMap<Thread,Boolean>();
496 // just a hack for things like eclipse test-runner threads
497 for (Thread t : Thread.getAllStackTraces().keySet()) {
498 rogueThreads.put(t, true);
502 System.out.println("WARNING: you are using -Dtests.iter=n where n > 1, not all tests support this option.");
503 System.out.println("Some may crash or fail: this is not a bug.");
508 * Looks for leftover running threads, trying to kill them off,
509 * so they don't fail future tests.
510 * returns the number of rogue threads that it found.
512 private static int threadCleanup(String context) {
514 Thread[] stillRunning = new Thread[Thread.activeCount()+1];
518 if ((threadCount = Thread.enumerate(stillRunning)) > 1) {
519 while (threadCount == stillRunning.length) {
520 // truncated response
521 stillRunning = new Thread[stillRunning.length*2];
522 threadCount = Thread.enumerate(stillRunning);
525 for (int i = 0; i < threadCount; i++) {
526 Thread t = stillRunning[i];
529 !rogueThreads.containsKey(t) &&
530 t != Thread.currentThread() &&
531 // TODO: TimeLimitingCollector starts a thread statically.... WTF?!
532 !t.getName().equals("TimeLimitedCollector timer thread") &&
533 /* its ok to keep your searcher across test cases */
534 (t.getName().startsWith("LuceneTestCase") && context.startsWith("test method")) == false) {
535 System.err.println("WARNING: " + context + " left thread running: " + t);
536 rogueThreads.put(t, true);
538 if (t.getName().startsWith("LuceneTestCase")) {
539 System.err.println("PLEASE CLOSE YOUR INDEXSEARCHERS IN YOUR TEST!!!!");
542 // wait on the thread to die of natural causes
544 t.join(THREAD_STOP_GRACE_MSEC);
545 } catch (InterruptedException e) { e.printStackTrace(); }
547 // try to stop the thread:
548 t.setUncaughtExceptionHandler(null);
549 Thread.setDefaultUncaughtExceptionHandler(null);
558 * Asserts that FieldCacheSanityChecker does not detect any
559 * problems with FieldCache.DEFAULT.
561 * If any problems are found, they are logged to System.err
562 * (allong with the msg) when the Assertion is thrown.
565 * This method is called by tearDown after every test method,
566 * however IndexReaders scoped inside test methods may be garbage
567 * collected prior to this method being called, causing errors to
568 * be overlooked. Tests are encouraged to keep their IndexReaders
569 * scoped at the class level, or to explicitly call this method
570 * directly in the same scope as the IndexReader.
573 * @see org.apache.lucene.util.FieldCacheSanityChecker
575 protected void assertSaneFieldCaches(final String msg) {
576 final CacheEntry[] entries = FieldCache.DEFAULT.getCacheEntries();
577 Insanity[] insanity = null;
580 insanity = FieldCacheSanityChecker.checkSanity(entries);
581 } catch (RuntimeException e) {
582 dumpArray(msg + ": FieldCache", entries, System.err);
586 assertEquals(msg + ": Insane FieldCache usage(s) found",
591 // report this in the event of any exception/failure
592 // if no failure, then insanity will be null anyway
593 if (null != insanity) {
594 dumpArray(msg + ": Insane FieldCache usage(s)", insanity, System.err);
601 * Returns a number of at least <code>i</code>
603 * The actual number returned will be influenced by whether {@link #TEST_NIGHTLY}
604 * is active and {@link #RANDOM_MULTIPLIER}, but also with some random fudge.
606 public static int atLeast(Random random, int i) {
607 int min = (TEST_NIGHTLY ? 5*i : i) * RANDOM_MULTIPLIER;
608 int max = min+(min/2);
609 return _TestUtil.nextInt(random, min, max);
612 public static int atLeast(int i) {
613 return atLeast(random, i);
617 * Returns true if something should happen rarely,
619 * The actual number returned will be influenced by whether {@link #TEST_NIGHTLY}
620 * is active and {@link #RANDOM_MULTIPLIER}.
622 public static boolean rarely(Random random) {
623 int p = TEST_NIGHTLY ? 25 : 5;
624 p += (p * Math.log(RANDOM_MULTIPLIER));
625 int min = 100 - Math.min(p, 90); // never more than 90
626 return random.nextInt(100) >= min;
629 public static boolean rarely() {
630 return rarely(random);
633 public static boolean usually(Random random) {
634 return !rarely(random);
637 public static boolean usually() {
638 return usually(random);
641 // These deprecated methods should be removed soon, when all tests using no Epsilon are fixed:
644 static public void assertEquals(double expected, double actual) {
645 assertEquals(null, expected, actual);
649 static public void assertEquals(String message, double expected, double actual) {
650 assertEquals(message, Double.valueOf(expected), Double.valueOf(actual));
654 static public void assertEquals(float expected, float actual) {
655 assertEquals(null, expected, actual);
659 static public void assertEquals(String message, float expected, float actual) {
660 assertEquals(message, Float.valueOf(expected), Float.valueOf(actual));
663 // Replacement for Assume jUnit class, so we can add a message with explanation:
665 private static final class TestIgnoredException extends RuntimeException {
666 TestIgnoredException(String msg) {
670 TestIgnoredException(String msg, Throwable t) {
675 public String getMessage() {
676 StringBuilder sb = new StringBuilder(super.getMessage());
677 if (getCause() != null)
678 sb.append(" - ").append(getCause());
679 return sb.toString();
682 // only this one is called by our code, exception is not used outside this class:
684 public void printStackTrace(PrintStream s) {
685 if (getCause() != null) {
686 s.println(super.toString() + " - Caused by:");
687 getCause().printStackTrace(s);
689 super.printStackTrace(s);
694 public static void assumeTrue(String msg, boolean b) {
695 Assume.assumeNoException(b ? null : new TestIgnoredException(msg));
698 public static void assumeFalse(String msg, boolean b) {
702 public static void assumeNoException(String msg, Exception e) {
703 Assume.assumeNoException(e == null ? null : new TestIgnoredException(msg, e));
707 * Convenience method for logging an iterator.
709 * @param label String logged before/after the items in the iterator
710 * @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.
711 * @param stream Stream to log messages to.
713 public static void dumpIterator(String label, Iterator<?> iter,
714 PrintStream stream) {
715 stream.println("*** BEGIN " + label + " ***");
717 stream.println(" ... NULL ...");
719 while (iter.hasNext()) {
720 stream.println(iter.next().toString());
723 stream.println("*** END " + label + " ***");
727 * Convenience method for logging an array. Wraps the array in an iterator and delegates
729 * @see #dumpIterator(String,Iterator,PrintStream)
731 public static void dumpArray(String label, Object[] objs,
732 PrintStream stream) {
733 Iterator<?> iter = (null == objs) ? null : Arrays.asList(objs).iterator();
734 dumpIterator(label, iter, stream);
737 /** create a new index writer config with random defaults */
738 public static IndexWriterConfig newIndexWriterConfig(Version v, Analyzer a) {
739 return newIndexWriterConfig(random, v, a);
742 /** create a new index writer config with random defaults using the specified random */
743 public static IndexWriterConfig newIndexWriterConfig(Random r, Version v, Analyzer a) {
744 IndexWriterConfig c = new IndexWriterConfig(v, a);
745 if (r.nextBoolean()) {
746 c.setMergePolicy(newTieredMergePolicy());
747 } else if (r.nextBoolean()) {
748 c.setMergePolicy(newLogMergePolicy());
750 c.setMergePolicy(new MockRandomMergePolicy(r));
753 if (r.nextBoolean()) {
754 c.setMergeScheduler(new SerialMergeScheduler());
756 if (r.nextBoolean()) {
759 c.setMaxBufferedDocs(_TestUtil.nextInt(r, 2, 7));
762 c.setMaxBufferedDocs(_TestUtil.nextInt(r, 8, 1000));
765 if (r.nextBoolean()) {
768 c.setTermIndexInterval(random.nextBoolean() ? _TestUtil.nextInt(r, 1, 31) : _TestUtil.nextInt(r, 129, 1000));
771 c.setTermIndexInterval(_TestUtil.nextInt(r, 32, 128));
774 if (r.nextBoolean()) {
775 c.setMaxThreadStates(_TestUtil.nextInt(r, 1, 20));
778 if (r.nextBoolean()) {
779 c.setMergePolicy(new MockRandomMergePolicy(r));
781 c.setMergePolicy(newLogMergePolicy());
784 c.setReaderPooling(r.nextBoolean());
785 c.setReaderTermsIndexDivisor(_TestUtil.nextInt(r, 1, 4));
789 public static LogMergePolicy newLogMergePolicy() {
790 return newLogMergePolicy(random);
793 public static TieredMergePolicy newTieredMergePolicy() {
794 return newTieredMergePolicy(random);
797 public static LogMergePolicy newLogMergePolicy(Random r) {
798 LogMergePolicy logmp = r.nextBoolean() ? new LogDocMergePolicy() : new LogByteSizeMergePolicy();
799 logmp.setUseCompoundFile(r.nextBoolean());
800 logmp.setCalibrateSizeByDeletes(r.nextBoolean());
802 logmp.setMergeFactor(_TestUtil.nextInt(r, 2, 4));
804 logmp.setMergeFactor(_TestUtil.nextInt(r, 5, 50));
809 public static TieredMergePolicy newTieredMergePolicy(Random r) {
810 TieredMergePolicy tmp = new TieredMergePolicy();
812 tmp.setMaxMergeAtOnce(_TestUtil.nextInt(r, 2, 4));
813 tmp.setMaxMergeAtOnceExplicit(_TestUtil.nextInt(r, 2, 4));
815 tmp.setMaxMergeAtOnce(_TestUtil.nextInt(r, 5, 50));
816 tmp.setMaxMergeAtOnceExplicit(_TestUtil.nextInt(r, 5, 50));
818 tmp.setMaxMergedSegmentMB(0.2 + r.nextDouble() * 2.0);
819 tmp.setFloorSegmentMB(0.2 + r.nextDouble() * 2.0);
820 tmp.setExpungeDeletesPctAllowed(0.0 + r.nextDouble() * 30.0);
821 tmp.setSegmentsPerTier(_TestUtil.nextInt(r, 2, 20));
822 tmp.setUseCompoundFile(r.nextBoolean());
823 tmp.setNoCFSRatio(0.1 + r.nextDouble()*0.8);
824 tmp.setReclaimDeletesWeight(r.nextDouble()*4);
828 public static LogMergePolicy newLogMergePolicy(boolean useCFS) {
829 LogMergePolicy logmp = newLogMergePolicy();
830 logmp.setUseCompoundFile(useCFS);
834 public static LogMergePolicy newLogMergePolicy(boolean useCFS, int mergeFactor) {
835 LogMergePolicy logmp = newLogMergePolicy();
836 logmp.setUseCompoundFile(useCFS);
837 logmp.setMergeFactor(mergeFactor);
841 public static LogMergePolicy newLogMergePolicy(int mergeFactor) {
842 LogMergePolicy logmp = newLogMergePolicy();
843 logmp.setMergeFactor(mergeFactor);
848 * Returns a new Directory instance. Use this when the test does not
849 * care about the specific Directory implementation (most tests).
851 * The Directory is wrapped with {@link MockDirectoryWrapper}.
852 * By default this means it will be picky, such as ensuring that you
853 * properly close it and all open files in your test. It will emulate
854 * some features of Windows, such as not allowing open files to be
857 public static MockDirectoryWrapper newDirectory() throws IOException {
858 return newDirectory(random);
862 * Returns a new Directory instance, using the specified random.
863 * See {@link #newDirectory()} for more information.
865 public static MockDirectoryWrapper newDirectory(Random r) throws IOException {
866 Directory impl = newDirectoryImpl(r, TEST_DIRECTORY);
867 MockDirectoryWrapper dir = new MockDirectoryWrapper(r, impl);
868 stores.put(dir, Thread.currentThread().getStackTrace());
873 * Returns a new Directory instance, with contents copied from the
874 * provided directory. See {@link #newDirectory()} for more
877 public static MockDirectoryWrapper newDirectory(Directory d) throws IOException {
878 return newDirectory(random, d);
881 /** Returns a new FSDirectory instance over the given file, which must be a folder. */
882 public static MockDirectoryWrapper newFSDirectory(File f) throws IOException {
883 return newFSDirectory(f, null);
886 /** Returns a new FSDirectory instance over the given file, which must be a folder. */
887 public static MockDirectoryWrapper newFSDirectory(File f, LockFactory lf) throws IOException {
888 String fsdirClass = TEST_DIRECTORY;
889 if (fsdirClass.equals("random")) {
890 fsdirClass = FS_DIRECTORIES[random.nextInt(FS_DIRECTORIES.length)];
893 if (fsdirClass.indexOf(".") == -1) {// if not fully qualified, assume .store
894 fsdirClass = "org.apache.lucene.store." + fsdirClass;
897 Class<? extends FSDirectory> clazz;
900 clazz = Class.forName(fsdirClass).asSubclass(FSDirectory.class);
901 } catch (ClassCastException e) {
902 // TEST_DIRECTORY is not a sub-class of FSDirectory, so draw one at random
903 fsdirClass = FS_DIRECTORIES[random.nextInt(FS_DIRECTORIES.length)];
905 if (fsdirClass.indexOf(".") == -1) {// if not fully qualified, assume .store
906 fsdirClass = "org.apache.lucene.store." + fsdirClass;
909 clazz = Class.forName(fsdirClass).asSubclass(FSDirectory.class);
911 MockDirectoryWrapper dir = new MockDirectoryWrapper(random, newFSDirectoryImpl(clazz, f));
913 dir.setLockFactory(lf);
915 stores.put(dir, Thread.currentThread().getStackTrace());
917 } catch (Exception e) {
918 throw new RuntimeException(e);
923 * Returns a new Directory instance, using the specified random
924 * with contents copied from the provided directory. See
925 * {@link #newDirectory()} for more information.
927 public static MockDirectoryWrapper newDirectory(Random r, Directory d) throws IOException {
928 Directory impl = newDirectoryImpl(r, TEST_DIRECTORY);
929 for (String file : d.listAll()) {
930 d.copy(impl, file, file);
932 MockDirectoryWrapper dir = new MockDirectoryWrapper(r, impl);
933 stores.put(dir, Thread.currentThread().getStackTrace());
937 /** Returns a new field instance.
938 * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */
939 public static Field newField(String name, String value, Index index) {
940 return newField(random, name, value, index);
943 /** Returns a new field instance.
944 * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */
945 public static Field newField(String name, String value, Store store, Index index) {
946 return newField(random, name, value, store, index);
950 * Returns a new Field instance. Use this when the test does not
951 * care about some specific field settings (most tests)
953 * <li>If the store value is set to Store.NO, sometimes the field will be randomly stored.
954 * <li>More term vector data than you ask for might be indexed, for example if you choose YES
955 * it might index term vectors with offsets too.
958 public static Field newField(String name, String value, Store store, Index index, TermVector tv) {
959 return newField(random, name, value, store, index, tv);
962 /** Returns a new field instance, using the specified random.
963 * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */
964 public static Field newField(Random random, String name, String value, Index index) {
965 return newField(random, name, value, Store.NO, index);
968 /** Returns a new field instance, using the specified random.
969 * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */
970 public static Field newField(Random random, String name, String value, Store store, Index index) {
971 return newField(random, name, value, store, index, TermVector.NO);
974 /** Returns a new field instance, using the specified random.
975 * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */
976 public static Field newField(Random random, String name, String value, Store store, Index index, TermVector tv) {
977 if (usually(random)) {
978 // most of the time, don't modify the params
979 return new Field(name, value, store, index, tv);
982 if (!index.isIndexed())
983 return new Field(name, value, store, index, tv);
985 if (!store.isStored() && random.nextBoolean())
986 store = Store.YES; // randomly store it
988 tv = randomTVSetting(random, tv);
990 return new Field(name, value, store, index, tv);
993 static final TermVector tvSettings[] = {
994 TermVector.NO, TermVector.YES, TermVector.WITH_OFFSETS,
995 TermVector.WITH_POSITIONS, TermVector.WITH_POSITIONS_OFFSETS
998 private static TermVector randomTVSetting(Random random, TermVector minimum) {
1000 case NO: return tvSettings[_TestUtil.nextInt(random, 0, tvSettings.length-1)];
1001 case YES: return tvSettings[_TestUtil.nextInt(random, 1, tvSettings.length-1)];
1002 case WITH_OFFSETS: return random.nextBoolean() ? TermVector.WITH_OFFSETS
1003 : TermVector.WITH_POSITIONS_OFFSETS;
1004 case WITH_POSITIONS: return random.nextBoolean() ? TermVector.WITH_POSITIONS
1005 : TermVector.WITH_POSITIONS_OFFSETS;
1006 default: return TermVector.WITH_POSITIONS_OFFSETS;
1010 /** return a random Locale from the available locales on the system */
1011 public static Locale randomLocale(Random random) {
1012 Locale locales[] = Locale.getAvailableLocales();
1013 return locales[random.nextInt(locales.length)];
1016 /** return a random TimeZone from the available timezones on the system */
1017 public static TimeZone randomTimeZone(Random random) {
1018 String tzIds[] = TimeZone.getAvailableIDs();
1019 return TimeZone.getTimeZone(tzIds[random.nextInt(tzIds.length)]);
1022 /** return a Locale object equivalent to its programmatic name */
1023 public static Locale localeForName(String localeName) {
1024 String elements[] = localeName.split("\\_");
1025 switch(elements.length) {
1026 case 3: return new Locale(elements[0], elements[1], elements[2]);
1027 case 2: return new Locale(elements[0], elements[1]);
1028 case 1: return new Locale(elements[0]);
1029 default: throw new IllegalArgumentException("Invalid Locale: " + localeName);
1033 private static final String FS_DIRECTORIES[] = {
1034 "SimpleFSDirectory",
1039 private static final String CORE_DIRECTORIES[] = {
1041 FS_DIRECTORIES[0], FS_DIRECTORIES[1], FS_DIRECTORIES[2]
1044 public static String randomDirectory(Random random) {
1045 if (rarely(random)) {
1046 return CORE_DIRECTORIES[random.nextInt(CORE_DIRECTORIES.length)];
1048 return "RAMDirectory";
1052 private static Directory newFSDirectoryImpl(
1053 Class<? extends FSDirectory> clazz, File file)
1054 throws IOException {
1055 FSDirectory d = null;
1057 // Assuming every FSDirectory has a ctor(File), but not all may take a
1058 // LockFactory too, so setting it afterwards.
1059 Constructor<? extends FSDirectory> ctor = clazz.getConstructor(File.class);
1060 d = ctor.newInstance(file);
1061 } catch (Exception e) {
1062 d = FSDirectory.open(file);
1067 /** Registers a temp file that will be deleted when tests are done. */
1068 public static void registerTempFile(File tmpFile) {
1069 tempDirs.put(tmpFile.getAbsoluteFile(), Thread.currentThread().getStackTrace());
1072 static Directory newDirectoryImpl(Random random, String clazzName) {
1073 if (clazzName.equals("random"))
1074 clazzName = randomDirectory(random);
1075 if (clazzName.indexOf(".") == -1) // if not fully qualified, assume .store
1076 clazzName = "org.apache.lucene.store." + clazzName;
1078 final Class<? extends Directory> clazz = Class.forName(clazzName).asSubclass(Directory.class);
1079 // If it is a FSDirectory type, try its ctor(File)
1080 if (FSDirectory.class.isAssignableFrom(clazz)) {
1081 final File tmpFile = _TestUtil.createTempFile("test", "tmp", TEMP_DIR);
1084 registerTempFile(tmpFile);
1085 return newFSDirectoryImpl(clazz.asSubclass(FSDirectory.class), tmpFile);
1089 return clazz.newInstance();
1090 } catch (Exception e) {
1091 throw new RuntimeException(e);
1095 /** create a new searcher over the reader.
1096 * This searcher might randomly use threads. */
1097 public static IndexSearcher newSearcher(IndexReader r) throws IOException {
1098 return newSearcher(r, true);
1101 /** create a new searcher over the reader.
1102 * This searcher might randomly use threads.
1103 * if <code>maybeWrap</code> is true, this searcher might wrap the reader
1104 * with one that returns null for getSequentialSubReaders.
1106 public static IndexSearcher newSearcher(IndexReader r, boolean maybeWrap) throws IOException {
1107 if (random.nextBoolean()) {
1108 if (maybeWrap && rarely()) {
1109 r = new SlowMultiReaderWrapper(r);
1111 return new AssertingIndexSearcher(r);
1114 final ExecutorService ex = (random.nextBoolean()) ? null
1115 : Executors.newFixedThreadPool(threads = _TestUtil.nextInt(random, 1, 8),
1116 new NamedThreadFactory("LuceneTestCase"));
1117 if (ex != null && VERBOSE) {
1118 System.out.println("NOTE: newSearcher using ExecutorService with " + threads + " threads");
1120 return new AssertingIndexSearcher(r, ex) {
1122 public void close() throws IOException {
1124 shutdownExecutorService(ex);
1130 static void shutdownExecutorService(ExecutorService ex) {
1134 ex.awaitTermination(1000, TimeUnit.MILLISECONDS);
1135 } catch (InterruptedException e) {
1136 e.printStackTrace();
1141 public String getName() {
1145 /** Gets a resource from the classpath as {@link File}. This method should only be used,
1146 * if a real file is needed. To get a stream, code should prefer
1147 * {@link Class#getResourceAsStream} using {@code this.getClass()}.
1150 protected File getDataFile(String name) throws IOException {
1152 return new File(this.getClass().getResource(name).toURI());
1153 } catch (Exception e) {
1154 throw new IOException("Cannot find resource: " + name);
1158 // We get here from InterceptTestCaseEvents on the 'failed' event....
1159 public void reportAdditionalFailureInfo() {
1160 System.err.println("NOTE: reproduce with: ant test -Dtestcase=" + getClass().getSimpleName()
1161 + " -Dtestmethod=" + getName() + " -Dtests.seed=" + new TwoLongs(staticSeed, seed)
1162 + reproduceWithExtraParams());
1165 // extra params that were overridden needed to reproduce the command
1166 private String reproduceWithExtraParams() {
1167 StringBuilder sb = new StringBuilder();
1168 if (!TEST_LOCALE.equals("random")) sb.append(" -Dtests.locale=").append(TEST_LOCALE);
1169 if (!TEST_TIMEZONE.equals("random")) sb.append(" -Dtests.timezone=").append(TEST_TIMEZONE);
1170 if (!TEST_DIRECTORY.equals("random")) sb.append(" -Dtests.directory=").append(TEST_DIRECTORY);
1171 if (RANDOM_MULTIPLIER > 1) sb.append(" -Dtests.multiplier=").append(RANDOM_MULTIPLIER);
1172 if (TEST_NIGHTLY) sb.append(" -Dtests.nightly=true");
1173 return sb.toString();
1176 // recorded seed: for beforeClass
1177 private static long staticSeed;
1178 // seed for individual test methods, changed in @before
1181 private static final Random seedRand = new Random();
1182 protected static final Random random = new Random(0);
1184 private String name = "<unknown>";
1187 * Annotation for tests that should only be run during nightly builds.
1191 @Retention(RetentionPolicy.RUNTIME)
1192 public @interface Nightly {}
1194 /** optionally filters the tests to be run by TEST_METHOD */
1195 public static class LuceneTestCaseRunner extends BlockJUnit4ClassRunner {
1196 private List<FrameworkMethod> testMethods;
1199 protected List<FrameworkMethod> computeTestMethods() {
1200 if (testMethods != null)
1202 testClassesRun.add(getTestClass().getJavaClass().getSimpleName());
1203 testMethods = new ArrayList<FrameworkMethod>();
1204 for (Method m : getTestClass().getJavaClass().getMethods()) {
1205 // check if the current test's class has methods annotated with @Ignore
1206 final Ignore ignored = m.getAnnotation(Ignore.class);
1207 if (ignored != null && !m.getName().equals("alwaysIgnoredTestMethod")) {
1208 System.err.println("NOTE: Ignoring test method '" + m.getName() + "': " + ignored.value());
1210 // add methods starting with "test"
1211 final int mod = m.getModifiers();
1212 if (m.getAnnotation(Test.class) != null ||
1213 (m.getName().startsWith("test") &&
1214 !Modifier.isAbstract(mod) &&
1215 m.getParameterTypes().length == 0 &&
1216 m.getReturnType() == Void.TYPE))
1218 if (Modifier.isStatic(mod))
1219 throw new RuntimeException("Test methods must not be static.");
1220 testMethods.add(new FrameworkMethod(m));
1224 if (testMethods.isEmpty()) {
1225 throw new RuntimeException("No runnable methods!");
1228 if (TEST_NIGHTLY == false) {
1229 if (getTestClass().getJavaClass().isAnnotationPresent(Nightly.class)) {
1230 /* the test class is annotated with nightly, remove all methods */
1231 String className = getTestClass().getJavaClass().getSimpleName();
1232 System.err.println("NOTE: Ignoring nightly-only test class '" + className + "'");
1233 testMethods.clear();
1235 /* remove all nightly-only methods */
1236 for (int i = 0; i < testMethods.size(); i++) {
1237 final FrameworkMethod m = testMethods.get(i);
1238 if (m.getAnnotation(Nightly.class) != null) {
1239 System.err.println("NOTE: Ignoring nightly-only test method '" + m.getName() + "'");
1240 testMethods.remove(i--);
1244 /* dodge a possible "no-runnable methods" exception by adding a fake ignored test */
1245 if (testMethods.isEmpty()) {
1247 testMethods.add(new FrameworkMethod(LuceneTestCase.class.getMethod("alwaysIgnoredTestMethod")));
1248 } catch (Exception e) { throw new RuntimeException(e); }
1255 protected void runChild(FrameworkMethod arg0, RunNotifier arg1) {
1257 System.out.println("\nNOTE: running test " + arg0.getName());
1260 // only print iteration info if the user requested more than one iterations
1261 final boolean verbose = VERBOSE && TEST_ITER > 1;
1263 final int currentIter[] = new int[1];
1264 arg1.addListener(new RunListener() {
1266 public void testFailure(Failure failure) throws Exception {
1268 System.out.println("\nNOTE: iteration " + currentIter[0] + " failed! ");
1272 for (int i = 0; i < TEST_ITER; i++) {
1275 System.out.println("\nNOTE: running iter=" + (1+i) + " of " + TEST_ITER);
1277 super.runChild(arg0, arg1);
1279 if (i >= TEST_ITER_MIN - 1) { // XXX is this still off-by-one?
1286 public LuceneTestCaseRunner(Class<?> clazz) throws InitializationError {
1288 Filter f = new Filter() {
1291 public String describe() { return "filters according to TEST_METHOD"; }
1294 public boolean shouldRun(Description d) {
1295 return TEST_METHOD == null || d.getMethodName().equals(TEST_METHOD);
1301 } catch (NoTestsRemainException e) {
1302 throw new RuntimeException(e);
1307 @Ignore("just a hack")
1308 public final void alwaysIgnoredTestMethod() {}