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 java.io.File; import java.io.IOException; import java.io.PrintStream; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Field.Index; import org.apache.lucene.document.Field.Store; import org.apache.lucene.document.Field.TermVector; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.LogByteSizeMergePolicy; import org.apache.lucene.index.LogDocMergePolicy; import org.apache.lucene.index.LogMergePolicy; import org.apache.lucene.index.MergePolicy; import org.apache.lucene.index.MockRandomMergePolicy; import org.apache.lucene.index.SerialMergeScheduler; import org.apache.lucene.index.SlowMultiReaderWrapper; import org.apache.lucene.index.TieredMergePolicy; import org.apache.lucene.search.AssertingIndexSearcher; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.FieldCache.CacheEntry; import org.apache.lucene.search.FieldCache; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.LockFactory; import org.apache.lucene.store.MockDirectoryWrapper; import org.apache.lucene.util.FieldCacheSanityChecker.Insanity; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.rules.TestWatchman; import org.junit.runner.RunWith; import org.junit.runners.model.FrameworkMethod; /** * Base class for all Lucene unit tests, Junit3 or Junit4 variant. * <p> * </p> * <p> * If you * override either <code>setUp()</code> or * <code>tearDown()</code> in your unit test, make sure you * call <code>super.setUp()</code> and * <code>super.tearDown()</code> * </p> * * <code>@After</code> - replaces setup * <code>@Before</code> - replaces teardown * <code>@Test</code> - any public method with this annotation is a test case, regardless * of its name * <p> * <p> * See Junit4 <a href="http://junit.org/junit/javadoc/4.7/">documentation</a> for a complete list of features. * <p> * Import from org.junit rather than junit.framework. * <p> * 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(String) */ @RunWith(LuceneTestCaseRunner.class) public abstract class LuceneTestCase extends Assert { /** * true iff tests are run in verbose mode. Note: if it is false, tests are not * expected to print any messages. */ public static final boolean VERBOSE = Boolean.getBoolean("tests.verbose"); /** Use this constant when creating Analyzers and any other version-dependent stuff. * <p><b>NOTE:</b> Change this when development starts for new Lucene version: */ public static final Version TEST_VERSION_CURRENT = Version.LUCENE_34; /** * If this is set, it is the only method that should run. */ static final String TEST_METHOD; /** Create indexes in this directory, optimally use a subdir, named after the test */ public static final File TEMP_DIR; static { String method = System.getProperty("testmethod", "").trim(); TEST_METHOD = method.length() == 0 ? null : method; String s = System.getProperty("tempDir", System.getProperty("java.io.tmpdir")); if (s == null) throw new RuntimeException("To run tests, you need to define system property 'tempDir' or 'java.io.tmpdir'."); TEMP_DIR = new File(s); TEMP_DIR.mkdirs(); } /** set of directories we created, in afterclass we try to clean these up */ private static final Map<File, StackTraceElement[]> tempDirs = Collections.synchronizedMap(new HashMap<File, StackTraceElement[]>()); // by default we randomly pick a different codec for // each test case (non-J4 tests) and each test class (J4 // tests) /** Gets the locale to run tests with */ public static final String TEST_LOCALE = System.getProperty("tests.locale", "random"); /** Gets the timezone to run tests with */ public static final String TEST_TIMEZONE = System.getProperty("tests.timezone", "random"); /** Gets the directory to run tests with */ public static final String TEST_DIRECTORY = System.getProperty("tests.directory", "random"); /** Get the number of times to run tests */ public static final int TEST_ITER = Integer.parseInt(System.getProperty("tests.iter", "1")); /** Get the minimum number of times to run tests until a failure happens */ public static final int TEST_ITER_MIN = Integer.parseInt(System.getProperty("tests.iter.min", Integer.toString(TEST_ITER))); /** Get the random seed for tests */ public static final String TEST_SEED = System.getProperty("tests.seed", "random"); /** whether or not nightly tests should run */ public static final boolean TEST_NIGHTLY = Boolean.parseBoolean(System.getProperty("tests.nightly", "false")); /** the line file used by LineFileDocs */ public static final String TEST_LINE_DOCS_FILE = System.getProperty("tests.linedocsfile", "europarl.lines.txt.gz"); /** whether or not to clean threads between test invocations: "false", "perMethod", "perClass" */ public static final String TEST_CLEAN_THREADS = System.getProperty("tests.cleanthreads", "perClass"); /** * A random multiplier which you should use when writing random tests: * multiply it by the number of iterations */ public static final int RANDOM_MULTIPLIER = Integer.parseInt(System.getProperty("tests.multiplier", "1")); private int savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount(); private volatile Thread.UncaughtExceptionHandler savedUncaughtExceptionHandler = null; /** Used to track if setUp and tearDown are called correctly from subclasses */ private static State state = State.INITIAL; private static enum State { INITIAL, // no tests ran yet SETUP, // test has called setUp() RANTEST, // test is running TEARDOWN // test has called tearDown() } private static class UncaughtExceptionEntry { public final Thread thread; public final Throwable exception; public UncaughtExceptionEntry(Thread thread, Throwable exception) { this.thread = thread; this.exception = exception; } } private List<UncaughtExceptionEntry> uncaughtExceptions = Collections.synchronizedList(new ArrayList<UncaughtExceptionEntry>()); private static Locale locale; private static Locale savedLocale; private static TimeZone timeZone; private static TimeZone savedTimeZone; protected static Map<MockDirectoryWrapper,StackTraceElement[]> stores; /** @deprecated: until we fix no-fork problems in solr tests */ @Deprecated static List<String> testClassesRun = new ArrayList<String>(); private static void initRandom() { assert !random.initialized; staticSeed = "random".equals(TEST_SEED) ? seedRand.nextLong() : ThreeLongs.fromString(TEST_SEED).l1; random.setSeed(staticSeed); random.initialized = true; } @Deprecated private static boolean icuTested = false; @BeforeClass public static void beforeClassLuceneTestCaseJ4() { initRandom(); state = State.INITIAL; tempDirs.clear(); stores = Collections.synchronizedMap(new IdentityHashMap<MockDirectoryWrapper,StackTraceElement[]>()); // enable this by default, for IDE consistency with ant tests (as its the default from ant) // TODO: really should be in solr base classes, but some extend LTC directly. // we do this in beforeClass, because some tests currently disable it if (System.getProperty("solr.directoryFactory") == null) { System.setProperty("solr.directoryFactory", "org.apache.solr.core.MockDirectoryFactory"); } // this code consumes randoms where 4.0's lucenetestcase would: to make seeds work across both branches. // TODO: doesn't completely work, because what if we get mockrandom codec?! if (random.nextInt(4) != 0) { random.nextInt(); // consume RandomCodecProvider's seed. } // end compatibility random-consumption savedLocale = Locale.getDefault(); // START hack to init ICU safely before we randomize locales. // ICU fails during classloading when a special Java7-only locale is the default // see: http://bugs.icu-project.org/trac/ticket/8734 if (!icuTested) { icuTested = true; try { Locale.setDefault(Locale.US); Class.forName("com.ibm.icu.util.ULocale"); } catch (ClassNotFoundException cnfe) { // ignore if no ICU is in classpath } } // END hack locale = TEST_LOCALE.equals("random") ? randomLocale(random) : localeForName(TEST_LOCALE); Locale.setDefault(locale); savedTimeZone = TimeZone.getDefault(); timeZone = TEST_TIMEZONE.equals("random") ? randomTimeZone(random) : TimeZone.getTimeZone(TEST_TIMEZONE); TimeZone.setDefault(timeZone); testsFailed = false; } @AfterClass public static void afterClassLuceneTestCaseJ4() { State oldState = state; // capture test execution state state = State.INITIAL; // set the state for subsequent tests Throwable problem = null; try { if (!testsFailed) { assertTrue("ensure your setUp() calls super.setUp() and your tearDown() calls super.tearDown()!!!", oldState == State.INITIAL || oldState == State.TEARDOWN); } } catch (Throwable t) { if (problem == null) problem = t; } if (! "false".equals(TEST_CLEAN_THREADS)) { int rogueThreads = threadCleanup("test class"); if (rogueThreads > 0) { // TODO: fail here once the leaks are fixed. System.err.println("RESOURCE LEAK: test class left " + rogueThreads + " thread(s) running"); } } Locale.setDefault(savedLocale); TimeZone.setDefault(savedTimeZone); System.clearProperty("solr.solr.home"); System.clearProperty("solr.data.dir"); try { // now look for unclosed resources if (!testsFailed) { checkResourcesAfterClass(); } } catch (Throwable t) { if (problem == null) problem = t; } stores = null; try { // clear out any temp directories if we can if (!testsFailed) { clearTempDirectoriesAfterClass(); } } catch (Throwable t) { if (problem == null) problem = t; } // if we had afterClass failures, get some debugging information if (problem != null) { reportPartialFailureInfo(); } // if verbose or tests failed, report some information back if (VERBOSE || testsFailed || problem != null) { printDebuggingInformation(); } // reset seed random.setSeed(0L); random.initialized = false; if (problem != null) { throw new RuntimeException(problem); } } /** print some useful debugging information about the environment */ private static void printDebuggingInformation() { System.err.println("NOTE: test params are: " + "locale=" + locale + ", timezone=" + (timeZone == null ? "(null)" : timeZone.getID())); System.err.println("NOTE: all tests run in this JVM:"); System.err.println(Arrays.toString(testClassesRun.toArray())); System.err.println("NOTE: " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch") + "/" + System.getProperty("java.vendor") + " " + System.getProperty("java.version") + " " + (Constants.JRE_IS_64BIT ? "(64-bit)" : "(32-bit)") + "/" + "cpus=" + Runtime.getRuntime().availableProcessors() + "," + "threads=" + Thread.activeCount() + "," + "free=" + Runtime.getRuntime().freeMemory() + "," + "total=" + Runtime.getRuntime().totalMemory()); } /** check that directories and their resources were closed */ private static void checkResourcesAfterClass() { for (MockDirectoryWrapper d : stores.keySet()) { if (d.isOpen()) { StackTraceElement elements[] = stores.get(d); // Look for the first class that is not LuceneTestCase that requested // a Directory. The first two items are of Thread's, so skipping over // them. StackTraceElement element = null; for (int i = 2; i < elements.length; i++) { StackTraceElement ste = elements[i]; if (ste.getClassName().indexOf("LuceneTestCase") == -1) { element = ste; break; } } fail("directory of test was not closed, opened from: " + element); } } } /** clear temp directories: this will fail if its not successful */ private static void clearTempDirectoriesAfterClass() { for (Entry<File, StackTraceElement[]> entry : tempDirs.entrySet()) { try { _TestUtil.rmDir(entry.getKey()); } catch (IOException e) { e.printStackTrace(); System.err.println("path " + entry.getKey() + " allocated from"); // first two STE's are Java's StackTraceElement[] elements = entry.getValue(); for (int i = 2; i < elements.length; i++) { StackTraceElement ste = elements[i]; // print only our code's stack information if (ste.getClassName().indexOf("org.apache.lucene") == -1) break; System.err.println("\t" + ste); } fail("could not remove temp dir: " + entry.getKey()); } } } protected static boolean testsFailed; /* true if any tests failed */ // This is how we get control when errors occur. // Think of this as start/end/success/failed // events. @Rule public final TestWatchman intercept = new TestWatchman() { @Override public void failed(Throwable e, FrameworkMethod method) { // org.junit.internal.AssumptionViolatedException in older releases // org.junit.Assume.AssumptionViolatedException in recent ones if (e.getClass().getName().endsWith("AssumptionViolatedException")) { if (e.getCause() instanceof _TestIgnoredException) e = e.getCause(); System.err.print("NOTE: Assume failed in '" + method.getName() + "' (ignored):"); if (VERBOSE) { System.err.println(); e.printStackTrace(System.err); } else { System.err.print(" "); System.err.println(e.getMessage()); } } else { testsFailed = true; reportAdditionalFailureInfo(); } super.failed(e, method); } @Override public void starting(FrameworkMethod method) { // set current method name for logging LuceneTestCase.this.name = method.getName(); State s = state; // capture test execution state state = State.RANTEST; // set the state for subsequent tests if (!testsFailed) { assertTrue("ensure your setUp() calls super.setUp()!!!", s == State.SETUP); } super.starting(method); } }; @Before public void setUp() throws Exception { seed = "random".equals(TEST_SEED) ? seedRand.nextLong() : ThreeLongs.fromString(TEST_SEED).l2; random.setSeed(seed); State s = state; // capture test execution state state = State.SETUP; // set the state for subsequent tests savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { testsFailed = true; uncaughtExceptions.add(new UncaughtExceptionEntry(t, e)); if (savedUncaughtExceptionHandler != null) savedUncaughtExceptionHandler.uncaughtException(t, e); } }); savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount(); if (!testsFailed) { assertTrue("ensure your tearDown() calls super.tearDown()!!!", (s == State.INITIAL || s == State.TEARDOWN)); } } /** * Forcible purges all cache entries from the FieldCache. * <p> * 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. * </p> * * @see FieldCache#purgeAllCaches() */ protected void purgeFieldCache(final FieldCache fc) { fc.purgeAllCaches(); } protected String getTestLabel() { return getClass().getName() + "." + getName(); } public static void setUseCompoundFile(MergePolicy mp, boolean useCompound) { if (mp instanceof LogMergePolicy) { ((LogMergePolicy) mp).setUseCompoundFile(useCompound); } else if (mp instanceof TieredMergePolicy) { ((TieredMergePolicy) mp).setUseCompoundFile(useCompound); } else { fail("MergePolicy (compound-file) not supported " + mp); } } public static void setMergeFactor(MergePolicy mp, int mergeFactor) { if (mp instanceof LogMergePolicy) { ((LogMergePolicy) mp).setMergeFactor(mergeFactor); } else if (mp instanceof TieredMergePolicy) { ((TieredMergePolicy) mp).setMaxMergeAtOnce(mergeFactor); ((TieredMergePolicy) mp).setMaxMergeAtOnceExplicit(mergeFactor); } else { fail("MergePolicy not supported " + mp); } } @After public void tearDown() throws Exception { State oldState = state; // capture test execution state state = State.TEARDOWN; // set the state for subsequent tests // NOTE: with junit 4.7, we don't get a reproduceWith because our Watchman // does not know if something fails in tearDown. so we ensure this happens ourselves for now. // we can remove this if we upgrade to 4.8 Throwable problem = null; try { if (!testsFailed) { // Note: we allow a test to go straight from SETUP -> TEARDOWN (without ever entering the RANTEST state) // because if you assume() inside setUp(), it skips the test and the TestWatchman has no way to know... assertTrue("ensure your setUp() calls super.setUp()!!!", oldState == State.RANTEST || oldState == State.SETUP); } } catch (Throwable t) { if (problem == null) problem = t; } BooleanQuery.setMaxClauseCount(savedBoolMaxClauseCount); // this won't throw any exceptions or fail the test // if we change this, then change this logic checkRogueThreadsAfter(); // restore the default uncaught exception handler Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler); try { checkUncaughtExceptionsAfter(); } catch (Throwable t) { if (problem == null) problem = t; } try { // calling assertSaneFieldCaches here isn't as useful as having test // classes call it directly from the scope where the index readers // are used, because they could be gc'ed just before this tearDown // method is called. // // But it's better then nothing. // // If you are testing functionality that you know for a fact // "violates" FieldCache sanity, then you should either explicitly // call purgeFieldCache at the end of your test method, or refactor // your Test class so that the inconsistant FieldCache usages are // isolated in distinct test methods assertSaneFieldCaches(getTestLabel()); } catch (Throwable t) { if (problem == null) problem = t; } purgeFieldCache(FieldCache.DEFAULT); if (problem != null) { testsFailed = true; reportAdditionalFailureInfo(); throw new RuntimeException(problem); } } /** check if the test still has threads running, we don't want them to * fail in a subsequent test and pass the blame to the wrong test */ private void checkRogueThreadsAfter() { if ("perMethod".equals(TEST_CLEAN_THREADS)) { int rogueThreads = threadCleanup("test method: '" + getName() + "'"); if (!testsFailed && rogueThreads > 0) { System.err.println("RESOURCE LEAK: test method: '" + getName() + "' left " + rogueThreads + " thread(s) running"); // TODO: fail, but print seed for now if (uncaughtExceptions.isEmpty()) { reportAdditionalFailureInfo(); } } } } /** see if any other threads threw uncaught exceptions, and fail the test if so */ private void checkUncaughtExceptionsAfter() { if (!uncaughtExceptions.isEmpty()) { System.err.println("The following exceptions were thrown by threads:"); for (UncaughtExceptionEntry entry : uncaughtExceptions) { System.err.println("*** Thread: " + entry.thread.getName() + " ***"); entry.exception.printStackTrace(System.err); } fail("Some threads threw uncaught exceptions!"); } } private final static int THREAD_STOP_GRACE_MSEC = 50; // jvm-wide list of 'rogue threads' we found, so they only get reported once. private final static IdentityHashMap<Thread,Boolean> rogueThreads = new IdentityHashMap<Thread,Boolean>(); static { // just a hack for things like eclipse test-runner threads for (Thread t : Thread.getAllStackTraces().keySet()) { rogueThreads.put(t, true); } if (TEST_ITER > 1) { System.out.println("WARNING: you are using -Dtests.iter=n where n > 1, not all tests support this option."); System.out.println("Some may crash or fail: this is not a bug."); } } /** * Looks for leftover running threads, trying to kill them off, * so they don't fail future tests. * returns the number of rogue threads that it found. */ private static int threadCleanup(String context) { // educated guess Thread[] stillRunning = new Thread[Thread.activeCount()+1]; int threadCount = 0; int rogueCount = 0; if ((threadCount = Thread.enumerate(stillRunning)) > 1) { while (threadCount == stillRunning.length) { // truncated response stillRunning = new Thread[stillRunning.length*2]; threadCount = Thread.enumerate(stillRunning); } for (int i = 0; i < threadCount; i++) { Thread t = stillRunning[i]; if (t.isAlive() && !rogueThreads.containsKey(t) && t != Thread.currentThread() && // TODO: TimeLimitingCollector starts a thread statically.... WTF?! !t.getName().equals("TimeLimitedCollector timer thread") && /* its ok to keep your searcher across test cases */ (t.getName().startsWith("LuceneTestCase") && context.startsWith("test method")) == false) { System.err.println("WARNING: " + context + " left thread running: " + t); rogueThreads.put(t, true); rogueCount++; if (t.getName().startsWith("LuceneTestCase")) { System.err.println("PLEASE CLOSE YOUR INDEXSEARCHERS IN YOUR TEST!!!!"); continue; } else { // wait on the thread to die of natural causes try { t.join(THREAD_STOP_GRACE_MSEC); } catch (InterruptedException e) { e.printStackTrace(); } } // try to stop the thread: t.setUncaughtExceptionHandler(null); Thread.setDefaultUncaughtExceptionHandler(null); t.interrupt(); } } } return rogueCount; } /** * Asserts that FieldCacheSanityChecker does not detect any * problems with FieldCache.DEFAULT. * <p> * If any problems are found, they are logged to System.err * (allong with the msg) when the Assertion is thrown. * </p> * <p> * 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. * </p> * * @see org.apache.lucene.util.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); } } } /** * Returns a number of at least <code>i</code> * <p> * The actual number returned will be influenced by whether {@link #TEST_NIGHTLY} * is active and {@link #RANDOM_MULTIPLIER}, but also with some random fudge. */ public static int atLeast(Random random, int i) { int min = (TEST_NIGHTLY ? 3*i : i) * RANDOM_MULTIPLIER; int max = min+(min/2); return _TestUtil.nextInt(random, min, max); } public static int atLeast(int i) { return atLeast(random, i); } /** * Returns true if something should happen rarely, * <p> * The actual number returned will be influenced by whether {@link #TEST_NIGHTLY} * is active and {@link #RANDOM_MULTIPLIER}. */ public static boolean rarely(Random random) { int p = TEST_NIGHTLY ? 10 : 5; p += (p * Math.log(RANDOM_MULTIPLIER)); int min = 100 - Math.min(p, 50); // never more than 50 return random.nextInt(100) >= min; } public static boolean rarely() { return rarely(random); } public static boolean usually(Random random) { return !rarely(random); } public static boolean usually() { return usually(random); } // These deprecated methods should be removed soon, when all tests using no Epsilon are fixed: @Deprecated static public void assertEquals(double expected, double actual) { assertEquals(null, expected, actual); } @Deprecated static public void assertEquals(String message, double expected, double actual) { assertEquals(message, Double.valueOf(expected), Double.valueOf(actual)); } @Deprecated static public void assertEquals(float expected, float actual) { assertEquals(null, expected, actual); } @Deprecated static public void assertEquals(String message, float expected, float actual) { assertEquals(message, Float.valueOf(expected), Float.valueOf(actual)); } public static void assumeTrue(String msg, boolean b) { Assume.assumeNoException(b ? null : new _TestIgnoredException(msg)); } public static void assumeFalse(String msg, boolean b) { assumeTrue(msg, !b); } public static void assumeNoException(String msg, Exception e) { Assume.assumeNoException(e == null ? null : new _TestIgnoredException(msg, e)); } /** * Convenience 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 + " ***"); } /** * Convenience 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); } /** create a new index writer config with random defaults */ public static IndexWriterConfig newIndexWriterConfig(Version v, Analyzer a) { return newIndexWriterConfig(random, v, a); } /** create a new index writer config with random defaults using the specified random */ public static IndexWriterConfig newIndexWriterConfig(Random r, Version v, Analyzer a) { IndexWriterConfig c = new IndexWriterConfig(v, a); if (r.nextBoolean()) { c.setMergePolicy(newTieredMergePolicy()); } else if (r.nextBoolean()) { c.setMergePolicy(newLogMergePolicy()); } else { c.setMergePolicy(new MockRandomMergePolicy(r)); } if (r.nextBoolean()) { c.setMergeScheduler(new SerialMergeScheduler()); } if (r.nextBoolean()) { if (rarely(r)) { // crazy value c.setMaxBufferedDocs(_TestUtil.nextInt(r, 2, 7)); } else { // reasonable value c.setMaxBufferedDocs(_TestUtil.nextInt(r, 8, 1000)); } } if (r.nextBoolean()) { if (rarely(r)) { // crazy value c.setTermIndexInterval(r.nextBoolean() ? _TestUtil.nextInt(r, 1, 31) : _TestUtil.nextInt(r, 129, 1000)); } else { // reasonable value c.setTermIndexInterval(_TestUtil.nextInt(r, 32, 128)); } } if (r.nextBoolean()) { c.setMaxThreadStates(_TestUtil.nextInt(r, 1, 20)); } if (r.nextBoolean()) { c.setMergePolicy(new MockRandomMergePolicy(r)); } else { c.setMergePolicy(newLogMergePolicy()); } c.setReaderPooling(r.nextBoolean()); c.setReaderTermsIndexDivisor(_TestUtil.nextInt(r, 1, 4)); return c; } public static LogMergePolicy newLogMergePolicy() { return newLogMergePolicy(random); } public static TieredMergePolicy newTieredMergePolicy() { return newTieredMergePolicy(random); } public static LogMergePolicy newLogMergePolicy(Random r) { LogMergePolicy logmp = r.nextBoolean() ? new LogDocMergePolicy() : new LogByteSizeMergePolicy(); logmp.setUseCompoundFile(r.nextBoolean()); logmp.setCalibrateSizeByDeletes(r.nextBoolean()); if (rarely(r)) { logmp.setMergeFactor(_TestUtil.nextInt(r, 2, 4)); } else { logmp.setMergeFactor(_TestUtil.nextInt(r, 5, 50)); } return logmp; } public static TieredMergePolicy newTieredMergePolicy(Random r) { TieredMergePolicy tmp = new TieredMergePolicy(); if (rarely(r)) { tmp.setMaxMergeAtOnce(_TestUtil.nextInt(r, 2, 4)); tmp.setMaxMergeAtOnceExplicit(_TestUtil.nextInt(r, 2, 4)); } else { tmp.setMaxMergeAtOnce(_TestUtil.nextInt(r, 5, 50)); tmp.setMaxMergeAtOnceExplicit(_TestUtil.nextInt(r, 5, 50)); } tmp.setMaxMergedSegmentMB(0.2 + r.nextDouble() * 2.0); tmp.setFloorSegmentMB(0.2 + r.nextDouble() * 2.0); // LUCENE-3577: //tmp.setExpungeDeletesPctAllowed(0.0 + r.nextDouble() * 30.0); try { Class<?> clazz = Class.forName("org.apache.lucene.index.TieredMergePolicy"); Method m = clazz.getMethod("setForceMergeDeletesPctAllowed", double.class); m.invoke(tmp, 0.0 + r.nextDouble() * 30.0); } catch (Exception e) { throw new RuntimeException(e); } tmp.setSegmentsPerTier(_TestUtil.nextInt(r, 2, 20)); tmp.setUseCompoundFile(r.nextBoolean()); tmp.setNoCFSRatio(0.1 + r.nextDouble()*0.8); tmp.setReclaimDeletesWeight(r.nextDouble()*4); return tmp; } public static LogMergePolicy newLogMergePolicy(boolean useCFS) { LogMergePolicy logmp = newLogMergePolicy(); logmp.setUseCompoundFile(useCFS); return logmp; } public static LogMergePolicy newLogMergePolicy(boolean useCFS, int mergeFactor) { LogMergePolicy logmp = newLogMergePolicy(); logmp.setUseCompoundFile(useCFS); logmp.setMergeFactor(mergeFactor); return logmp; } public static LogMergePolicy newLogMergePolicy(int mergeFactor) { LogMergePolicy logmp = newLogMergePolicy(); logmp.setMergeFactor(mergeFactor); return logmp; } /** * Returns a new Directory instance. Use this when the test does not * care about the specific Directory implementation (most tests). * <p> * The Directory is wrapped with {@link MockDirectoryWrapper}. * By default this means it will be picky, such as ensuring that you * properly close it and all open files in your test. It will emulate * some features of Windows, such as not allowing open files to be * overwritten. */ public static MockDirectoryWrapper newDirectory() throws IOException { return newDirectory(random); } /** * Returns a new Directory instance, using the specified random. * See {@link #newDirectory()} for more information. */ public static MockDirectoryWrapper newDirectory(Random r) throws IOException { Directory impl = newDirectoryImpl(r, TEST_DIRECTORY); MockDirectoryWrapper dir = new MockDirectoryWrapper(r, impl); stores.put(dir, Thread.currentThread().getStackTrace()); return dir; } /** * Returns a new Directory instance, with contents copied from the * provided directory. See {@link #newDirectory()} for more * information. */ public static MockDirectoryWrapper newDirectory(Directory d) throws IOException { return newDirectory(random, d); } /** Returns a new FSDirectory instance over the given file, which must be a folder. */ public static MockDirectoryWrapper newFSDirectory(File f) throws IOException { return newFSDirectory(f, null); } /** Returns a new FSDirectory instance over the given file, which must be a folder. */ public static MockDirectoryWrapper newFSDirectory(File f, LockFactory lf) throws IOException { String fsdirClass = TEST_DIRECTORY; if (fsdirClass.equals("random")) { fsdirClass = FS_DIRECTORIES[random.nextInt(FS_DIRECTORIES.length)]; } if (fsdirClass.indexOf(".") == -1) {// if not fully qualified, assume .store fsdirClass = "org.apache.lucene.store." + fsdirClass; } Class<? extends FSDirectory> clazz; try { try { clazz = Class.forName(fsdirClass).asSubclass(FSDirectory.class); } catch (ClassCastException e) { // TEST_DIRECTORY is not a sub-class of FSDirectory, so draw one at random fsdirClass = FS_DIRECTORIES[random.nextInt(FS_DIRECTORIES.length)]; if (fsdirClass.indexOf(".") == -1) {// if not fully qualified, assume .store fsdirClass = "org.apache.lucene.store." + fsdirClass; } clazz = Class.forName(fsdirClass).asSubclass(FSDirectory.class); } MockDirectoryWrapper dir = new MockDirectoryWrapper(random, newFSDirectoryImpl(clazz, f)); if (lf != null) { dir.setLockFactory(lf); } stores.put(dir, Thread.currentThread().getStackTrace()); return dir; } catch (Exception e) { throw new RuntimeException(e); } } /** * Returns a new Directory instance, using the specified random * with contents copied from the provided directory. See * {@link #newDirectory()} for more information. */ public static MockDirectoryWrapper newDirectory(Random r, Directory d) throws IOException { Directory impl = newDirectoryImpl(r, TEST_DIRECTORY); for (String file : d.listAll()) { d.copy(impl, file, file); } MockDirectoryWrapper dir = new MockDirectoryWrapper(r, impl); stores.put(dir, Thread.currentThread().getStackTrace()); return dir; } /** Returns a new field instance. * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */ public static Field newField(String name, String value, Index index) { return newField(random, name, value, index); } /** Returns a new field instance. * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */ public static Field newField(String name, String value, Store store, Index index) { return newField(random, name, value, store, index); } /** * Returns a new Field instance. Use this when the test does not * care about some specific field settings (most tests) * <ul> * <li>If the store value is set to Store.NO, sometimes the field will be randomly stored. * <li>More term vector data than you ask for might be indexed, for example if you choose YES * it might index term vectors with offsets too. * </ul> */ public static Field newField(String name, String value, Store store, Index index, TermVector tv) { return newField(random, name, value, store, index, tv); } /** Returns a new field instance, using the specified random. * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */ public static Field newField(Random random, String name, String value, Index index) { return newField(random, name, value, Store.NO, index); } /** Returns a new field instance, using the specified random. * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */ public static Field newField(Random random, String name, String value, Store store, Index index) { return newField(random, name, value, store, index, TermVector.NO); } /** Returns a new field instance, using the specified random. * See {@link #newField(String, String, Field.Store, Field.Index, Field.TermVector)} for more information */ public static Field newField(Random random, String name, String value, Store store, Index index, TermVector tv) { if (usually(random)) { // most of the time, don't modify the params return new Field(name, value, store, index, tv); } if (!index.isIndexed()) return new Field(name, value, store, index, tv); if (!store.isStored() && random.nextBoolean()) store = Store.YES; // randomly store it tv = randomTVSetting(random, tv); return new Field(name, value, store, index, tv); } static final TermVector tvSettings[] = { TermVector.NO, TermVector.YES, TermVector.WITH_OFFSETS, TermVector.WITH_POSITIONS, TermVector.WITH_POSITIONS_OFFSETS }; private static TermVector randomTVSetting(Random random, TermVector minimum) { switch(minimum) { case NO: return tvSettings[_TestUtil.nextInt(random, 0, tvSettings.length-1)]; case YES: return tvSettings[_TestUtil.nextInt(random, 1, tvSettings.length-1)]; case WITH_OFFSETS: return random.nextBoolean() ? TermVector.WITH_OFFSETS : TermVector.WITH_POSITIONS_OFFSETS; case WITH_POSITIONS: return random.nextBoolean() ? TermVector.WITH_POSITIONS : TermVector.WITH_POSITIONS_OFFSETS; default: return TermVector.WITH_POSITIONS_OFFSETS; } } /** return a random Locale from the available locales on the system */ public static Locale randomLocale(Random random) { Locale locales[] = Locale.getAvailableLocales(); return locales[random.nextInt(locales.length)]; } /** return a random TimeZone from the available timezones on the system */ public static TimeZone randomTimeZone(Random random) { String tzIds[] = TimeZone.getAvailableIDs(); return TimeZone.getTimeZone(tzIds[random.nextInt(tzIds.length)]); } /** return a Locale object equivalent to its programmatic name */ public static Locale localeForName(String localeName) { String elements[] = localeName.split("\\_"); switch(elements.length) { case 3: return new Locale(elements[0], elements[1], elements[2]); case 2: return new Locale(elements[0], elements[1]); case 1: return new Locale(elements[0]); default: throw new IllegalArgumentException("Invalid Locale: " + localeName); } } private static final String FS_DIRECTORIES[] = { "SimpleFSDirectory", "NIOFSDirectory", "MMapDirectory" }; private static final String CORE_DIRECTORIES[] = { "RAMDirectory", FS_DIRECTORIES[0], FS_DIRECTORIES[1], FS_DIRECTORIES[2] }; public static String randomDirectory(Random random) { if (rarely(random)) { return CORE_DIRECTORIES[random.nextInt(CORE_DIRECTORIES.length)]; } else { return "RAMDirectory"; } } private static Directory newFSDirectoryImpl( Class<? extends FSDirectory> clazz, File file) throws IOException { FSDirectory d = null; try { // Assuming every FSDirectory has a ctor(File), but not all may take a // LockFactory too, so setting it afterwards. Constructor<? extends FSDirectory> ctor = clazz.getConstructor(File.class); d = ctor.newInstance(file); } catch (Exception e) { d = FSDirectory.open(file); } return d; } /** Registers a temp file that will be deleted when tests are done. */ public static void registerTempFile(File tmpFile) { tempDirs.put(tmpFile.getAbsoluteFile(), Thread.currentThread().getStackTrace()); } static Directory newDirectoryImpl(Random random, String clazzName) { if (clazzName.equals("random")) clazzName = randomDirectory(random); if (clazzName.indexOf(".") == -1) // if not fully qualified, assume .store clazzName = "org.apache.lucene.store." + clazzName; try { final Class<? extends Directory> clazz = Class.forName(clazzName).asSubclass(Directory.class); // If it is a FSDirectory type, try its ctor(File) if (FSDirectory.class.isAssignableFrom(clazz)) { final File tmpFile = _TestUtil.createTempFile("test", "tmp", TEMP_DIR); tmpFile.delete(); tmpFile.mkdir(); registerTempFile(tmpFile); return newFSDirectoryImpl(clazz.asSubclass(FSDirectory.class), tmpFile); } // try empty ctor return clazz.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } /** create a new searcher over the reader. * This searcher might randomly use threads. */ public static IndexSearcher newSearcher(IndexReader r) throws IOException { return newSearcher(r, true); } /** create a new searcher over the reader. * This searcher might randomly use threads. * if <code>maybeWrap</code> is true, this searcher might wrap the reader * with one that returns null for getSequentialSubReaders. */ public static IndexSearcher newSearcher(IndexReader r, boolean maybeWrap) throws IOException { if (random.nextBoolean()) { if (maybeWrap && rarely()) { r = new SlowMultiReaderWrapper(r); } return new AssertingIndexSearcher(r); } else { int threads = 0; final ExecutorService ex = (random.nextBoolean()) ? null : Executors.newFixedThreadPool(threads = _TestUtil.nextInt(random, 1, 8), new NamedThreadFactory("LuceneTestCase")); if (ex != null && VERBOSE) { System.out.println("NOTE: newSearcher using ExecutorService with " + threads + " threads"); } return new AssertingIndexSearcher(r, ex) { @Override public void close() throws IOException { super.close(); shutdownExecutorService(ex); } }; } } static void shutdownExecutorService(ExecutorService ex) { if (ex != null) { ex.shutdown(); try { ex.awaitTermination(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } } public String getName() { return this.name; } /** Gets a resource from the classpath as {@link File}. This method should only be used, * if a real file is needed. To get a stream, code should prefer * {@link Class#getResourceAsStream} using {@code this.getClass()}. */ protected File getDataFile(String name) throws IOException { try { return new File(this.getClass().getResource(name).toURI()); } catch (Exception e) { throw new IOException("Cannot find resource: " + name); } } // We get here from InterceptTestCaseEvents on the 'failed' event.... public static void reportPartialFailureInfo() { System.err.println("NOTE: reproduce with (hopefully): ant test -Dtestcase=" + testClassesRun.get(testClassesRun.size()-1) + " -Dtests.seed=" + new ThreeLongs(staticSeed, 0L, LuceneTestCaseRunner.runnerSeed) + reproduceWithExtraParams()); } // We get here from InterceptTestCaseEvents on the 'failed' event.... public void reportAdditionalFailureInfo() { System.err.println("NOTE: reproduce with: ant test -Dtestcase=" + getClass().getSimpleName() + " -Dtestmethod=" + getName() + " -Dtests.seed=" + new ThreeLongs(staticSeed, seed, LuceneTestCaseRunner.runnerSeed) + reproduceWithExtraParams()); } // extra params that were overridden needed to reproduce the command private static String reproduceWithExtraParams() { StringBuilder sb = new StringBuilder(); if (!TEST_LOCALE.equals("random")) sb.append(" -Dtests.locale=").append(TEST_LOCALE); if (!TEST_TIMEZONE.equals("random")) sb.append(" -Dtests.timezone=").append(TEST_TIMEZONE); if (!TEST_DIRECTORY.equals("random")) sb.append(" -Dtests.directory=").append(TEST_DIRECTORY); if (RANDOM_MULTIPLIER > 1) sb.append(" -Dtests.multiplier=").append(RANDOM_MULTIPLIER); if (TEST_NIGHTLY) sb.append(" -Dtests.nightly=true"); return sb.toString(); } // recorded seed: for beforeClass private static long staticSeed; // seed for individual test methods, changed in @before private long seed; static final Random seedRand = new Random(); protected static final SmartRandom random = new SmartRandom(0); private String name = "<unknown>"; /** * Annotation for tests that should only be run during nightly builds. */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface Nightly {} @Ignore("just a hack") public final void alwaysIgnoredTestMethod() {} }