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.PrintStream; import java.io.File; import java.util.Arrays; import java.util.Iterator; import java.util.Random; import junit.framework.TestCase; 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; /** * Base class for all Lucene unit tests. * <p> * Currently the * only added functionality over JUnit's TestCase is * asserting that no unhandled exceptions occurred in * threads launched by ConcurrentMergeScheduler and asserting sane * FieldCache usage athe moment of tearDown. * </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> * @see #assertSaneFieldCaches */ public abstract class LuceneTestCase extends TestCase { public static final File TEMP_DIR; static { 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); } public LuceneTestCase() { super(); } public LuceneTestCase(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); ConcurrentMergeScheduler.setTestMode(); } /** * 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(); } @Override protected 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); } super.tearDown(); } /** * 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 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); } @Override public void runBare() throws Throwable { try { seed = null; super.runBare(); } catch (Throwable e) { if (seed != null) { System.out.println("NOTE: random seed of testcase '" + getName() + "' was: " + seed); } throw e; } } // recorded seed protected Long seed = null; // static members private static final Random seedRnd = new Random(); }