add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / backwards / src / test-framework / org / apache / lucene / util / LuceneTestCase.java
1 package org.apache.lucene.util;
2
3 /**
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 import java.io.File;
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;
30 import java.util.*;
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;
80
81 /**
82  * Base class for all Lucene unit tests, Junit3 or Junit4 variant.
83  * <p>
84  * </p>
85  * <p>
86  * If you
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>
91  * </p>
92  *
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
96  * of its name
97  * <p>
98  * <p>
99  * See Junit4 <a href="http://junit.org/junit/javadoc/4.7/">documentation</a> for a complete list of features.
100  * <p>
101  * Import from org.junit rather than junit.framework.
102  * <p>
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)
106  */
107
108 @RunWith(LuceneTestCase.LuceneTestCaseRunner.class)
109 public abstract class LuceneTestCase extends Assert {
110
111   /**
112    * true iff tests are run in verbose mode. Note: if it is false, tests are not
113    * expected to print any messages.
114    */
115   public static final boolean VERBOSE = Boolean.getBoolean("tests.verbose");
116
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:
119    */
120   public static final Version TEST_VERSION_CURRENT = Version.LUCENE_33;
121
122   /**
123    * If this is set, it is the only method that should run.
124    */
125   static final String TEST_METHOD;
126   
127   /** Create indexes in this directory, optimally use a subdir, named after the test */
128   public static final File TEMP_DIR;
129   static {
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"));
133     if (s == null)
134       throw new RuntimeException("To run tests, you need to define system property 'tempDir' or 'java.io.tmpdir'.");
135     TEMP_DIR = new File(s);
136     TEMP_DIR.mkdirs();
137   }
138   
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[]>());
141
142   // by default we randomly pick a different codec for
143   // each test case (non-J4 tests) and each test class (J4
144   // tests)
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");
163
164   /**
165    * A random multiplier which you should use when writing random tests:
166    * multiply it by the number of iterations
167    */
168   public static final int RANDOM_MULTIPLIER = Integer.parseInt(System.getProperty("tests.multiplier", "1"));
169   
170   private int savedBoolMaxClauseCount;
171
172   private volatile Thread.UncaughtExceptionHandler savedUncaughtExceptionHandler = null;
173   
174   /** Used to track if setUp and tearDown are called correctly from subclasses */
175   private static State state = State.INITIAL;
176
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()
182   }
183   
184   private static class UncaughtExceptionEntry {
185     public final Thread thread;
186     public final Throwable exception;
187     
188     public UncaughtExceptionEntry(Thread thread, Throwable exception) {
189       this.thread = thread;
190       this.exception = exception;
191     }
192   }
193   private List<UncaughtExceptionEntry> uncaughtExceptions = Collections.synchronizedList(new ArrayList<UncaughtExceptionEntry>());
194   
195   private static Locale locale;
196   private static Locale savedLocale;
197   private static TimeZone timeZone;
198   private static TimeZone savedTimeZone;
199   
200   protected static Map<MockDirectoryWrapper,StackTraceElement[]> stores;
201   
202   private static class TwoLongs {
203     public final long l1, l2;
204
205     public TwoLongs(long l1, long l2) {
206       this.l1 = l1;
207       this.l2 = l2;
208     }
209
210     @Override
211     public String toString() {
212       return l1 + ":" + l2;
213     }
214
215     public static TwoLongs fromString(String s) {
216       final int i = s.indexOf(':');
217       assert i != -1;
218       return new TwoLongs(Long.parseLong(s.substring(0, i)),
219                           Long.parseLong(s.substring(1+i)));
220     }
221   }
222
223   /** @deprecated: until we fix no-fork problems in solr tests */
224   @Deprecated
225   private static List<String> testClassesRun = new ArrayList<String>();
226   
227   @BeforeClass
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);
232     tempDirs.clear();
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");
239     }
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.
244     }
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);
252     testsFailed = false;
253   }
254   
255   @AfterClass
256   public static void afterClassLuceneTestCaseJ4() {
257     if (!testsFailed) {
258       assertTrue("ensure your setUp() calls super.setUp() and your tearDown() calls super.tearDown()!!!", 
259           state == State.INITIAL || state == State.TEARDOWN);
260     }
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");
267       }
268     }
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
274     if (!testsFailed)
275       for (MockDirectoryWrapper d : stores.keySet()) {
276         if (d.isOpen()) {
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
280           // them.
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) {
285               element = ste;
286               break;
287             }
288           }
289           fail("directory of test was not closed, opened from: " + element);
290         }
291       }
292     stores = null;
293     // if verbose or tests failed, report some information back
294     if (VERBOSE || testsFailed)
295       System.err.println("NOTE: test params are: " +
296         "locale=" + locale + 
297         ", timezone=" + (timeZone == null ? "(null)" : timeZone.getID()));
298     if (testsFailed) {
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());
311     }
312     // clear out any temp directories if we can
313     if (!testsFailed) {
314       for (Entry<File, StackTraceElement[]> entry : tempDirs.entrySet()) {
315         try {
316           _TestUtil.rmDir(entry.getKey());
317         } catch (IOException e) {
318           e.printStackTrace();
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);
327           }
328           fail("could not remove temp dir: " + entry.getKey());
329         }
330       }
331     }
332   }
333
334   private static boolean testsFailed; /* true if any tests failed */
335   
336   // This is how we get control when errors occur.
337   // Think of this as start/end/success/failed
338   // events.
339   @Rule
340   public final TestWatchman intercept = new TestWatchman() {
341
342     @Override
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)
348           e = e.getCause();
349         System.err.print("NOTE: Assume failed in '" + method.getName() + "' (ignored):");
350         if (VERBOSE) {
351           System.err.println();
352           e.printStackTrace(System.err);
353         } else {
354           System.err.print(" ");
355           System.err.println(e.getMessage());
356         }
357       } else {
358         testsFailed = true;
359         reportAdditionalFailureInfo();
360       }
361       super.failed(e, method);
362     }
363
364     @Override
365     public void starting(FrameworkMethod method) {
366       // set current method name for logging
367       LuceneTestCase.this.name = method.getName();
368       if (!testsFailed) {
369         assertTrue("ensure your setUp() calls super.setUp()!!!", state == State.SETUP);
370       }
371       state = State.RANTEST;
372       super.starting(method);
373     }
374   };
375
376   @Before
377   public void setUp() throws Exception {
378     seed = "random".equals(TEST_SEED) ? seedRand.nextLong() : TwoLongs.fromString(TEST_SEED).l2;
379     random.setSeed(seed);
380     if (!testsFailed) {
381       assertTrue("ensure your tearDown() calls super.tearDown()!!!", (state == State.INITIAL || state == State.TEARDOWN));
382     }
383     state = State.SETUP;
384     savedUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
385     Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
386       public void uncaughtException(Thread t, Throwable e) {
387         testsFailed = true;
388         uncaughtExceptions.add(new UncaughtExceptionEntry(t, e));
389         if (savedUncaughtExceptionHandler != null)
390           savedUncaughtExceptionHandler.uncaughtException(t, e);
391       }
392     });
393     
394     savedBoolMaxClauseCount = BooleanQuery.getMaxClauseCount();
395   }
396
397
398   /**
399    * Forcible purges all cache entries from the FieldCache.
400    * <p>
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.
405    * </p>
406    *
407    * @see FieldCache#purgeAllCaches()
408    */
409   protected void purgeFieldCache(final FieldCache fc) {
410     fc.purgeAllCaches();
411   }
412
413   protected String getTestLabel() {
414     return getClass().getName() + "." + getName();
415   }
416
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);
422     } else {
423       fail("MergePolicy (compound-file) not supported " + mp);
424     }
425   }
426
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);
433     } else {
434       fail("MergePolicy not supported " + mp);
435     }
436   }
437
438   @After
439   public void tearDown() throws Exception {
440     if (!testsFailed) {
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);
444     }
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();
455         }
456       }
457     }
458     Thread.setDefaultUncaughtExceptionHandler(savedUncaughtExceptionHandler);
459     try {
460
461       if (!uncaughtExceptions.isEmpty()) {
462         testsFailed = true;
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);
468         }
469         fail("Some threads threw uncaught exceptions!");
470       }
471
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 
475       // method is called.
476       //
477       // But it's better then nothing.
478       //
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());
485
486     } finally {
487       purgeFieldCache(FieldCache.DEFAULT);
488     }
489   }
490
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>();
494   
495   static {
496     // just a hack for things like eclipse test-runner threads
497     for (Thread t : Thread.getAllStackTraces().keySet()) {
498       rogueThreads.put(t, true);
499     }
500     
501     if (TEST_ITER > 1) {
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.");
504     }
505   }
506   
507   /**
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.
511    */
512   private static int threadCleanup(String context) {
513     // educated guess
514     Thread[] stillRunning = new Thread[Thread.activeCount()+1];
515     int threadCount = 0;
516     int rogueCount = 0;
517     
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);
523       }
524       
525       for (int i = 0; i < threadCount; i++) {
526         Thread t = stillRunning[i];
527           
528         if (t.isAlive() && 
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);
537           rogueCount++;
538           if (t.getName().startsWith("LuceneTestCase")) {
539             System.err.println("PLEASE CLOSE YOUR INDEXSEARCHERS IN YOUR TEST!!!!");
540             continue;
541           } else {
542             // wait on the thread to die of natural causes
543             try {
544               t.join(THREAD_STOP_GRACE_MSEC);
545             } catch (InterruptedException e) { e.printStackTrace(); }
546           }
547           // try to stop the thread:
548           t.setUncaughtExceptionHandler(null);
549           Thread.setDefaultUncaughtExceptionHandler(null);
550           t.interrupt();
551         }
552       }
553     }
554     return rogueCount;
555   }
556   
557   /**
558    * Asserts that FieldCacheSanityChecker does not detect any
559    * problems with FieldCache.DEFAULT.
560    * <p>
561    * If any problems are found, they are logged to System.err
562    * (allong with the msg) when the Assertion is thrown.
563    * </p>
564    * <p>
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.
571    * </p>
572    *
573    * @see org.apache.lucene.util.FieldCacheSanityChecker
574    */
575   protected void assertSaneFieldCaches(final String msg) {
576     final CacheEntry[] entries = FieldCache.DEFAULT.getCacheEntries();
577     Insanity[] insanity = null;
578     try {
579       try {
580         insanity = FieldCacheSanityChecker.checkSanity(entries);
581       } catch (RuntimeException e) {
582         dumpArray(msg + ": FieldCache", entries, System.err);
583         throw e;
584       }
585
586       assertEquals(msg + ": Insane FieldCache usage(s) found",
587               0, insanity.length);
588       insanity = null;
589     } finally {
590
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);
595       }
596
597     }
598   }
599   
600   /**
601    * Returns a number of at least <code>i</code>
602    * <p>
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.
605    */
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);
610   }
611   
612   public static int atLeast(int i) {
613     return atLeast(random, i);
614   }
615   
616   /**
617    * Returns true if something should happen rarely,
618    * <p>
619    * The actual number returned will be influenced by whether {@link #TEST_NIGHTLY}
620    * is active and {@link #RANDOM_MULTIPLIER}.
621    */
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;
627   }
628   
629   public static boolean rarely() {
630     return rarely(random);
631   }
632   
633   public static boolean usually(Random random) {
634     return !rarely(random);
635   }
636   
637   public static boolean usually() {
638     return usually(random);
639   }
640
641   // These deprecated methods should be removed soon, when all tests using no Epsilon are fixed:
642   
643   @Deprecated
644   static public void assertEquals(double expected, double actual) {
645     assertEquals(null, expected, actual);
646   }
647    
648   @Deprecated
649   static public void assertEquals(String message, double expected, double actual) {
650     assertEquals(message, Double.valueOf(expected), Double.valueOf(actual));
651   }
652
653   @Deprecated
654   static public void assertEquals(float expected, float actual) {
655     assertEquals(null, expected, actual);
656   }
657
658   @Deprecated
659   static public void assertEquals(String message, float expected, float actual) {
660     assertEquals(message, Float.valueOf(expected), Float.valueOf(actual));
661   }
662   
663   // Replacement for Assume jUnit class, so we can add a message with explanation:
664   
665   private static final class TestIgnoredException extends RuntimeException {
666     TestIgnoredException(String msg) {
667       super(msg);
668     }
669     
670     TestIgnoredException(String msg, Throwable t) {
671       super(msg, t);
672     }
673     
674     @Override
675     public String getMessage() {
676       StringBuilder sb = new StringBuilder(super.getMessage());
677       if (getCause() != null)
678         sb.append(" - ").append(getCause());
679       return sb.toString();
680     }
681     
682     // only this one is called by our code, exception is not used outside this class:
683     @Override
684     public void printStackTrace(PrintStream s) {
685       if (getCause() != null) {
686         s.println(super.toString() + " - Caused by:");
687         getCause().printStackTrace(s);
688       } else {
689         super.printStackTrace(s);
690       }
691     }
692   }
693   
694   public static void assumeTrue(String msg, boolean b) {
695     Assume.assumeNoException(b ? null : new TestIgnoredException(msg));
696   }
697  
698   public static void assumeFalse(String msg, boolean b) {
699     assumeTrue(msg, !b);
700   }
701   
702   public static void assumeNoException(String msg, Exception e) {
703     Assume.assumeNoException(e == null ? null : new TestIgnoredException(msg, e));
704   }
705  
706   /**
707    * Convenience method for logging an iterator.
708    *
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.
712    */
713   public static void dumpIterator(String label, Iterator<?> iter,
714                                   PrintStream stream) {
715     stream.println("*** BEGIN " + label + " ***");
716     if (null == iter) {
717       stream.println(" ... NULL ...");
718     } else {
719       while (iter.hasNext()) {
720         stream.println(iter.next().toString());
721       }
722     }
723     stream.println("*** END " + label + " ***");
724   }
725
726   /**
727    * Convenience method for logging an array.  Wraps the array in an iterator and delegates
728    *
729    * @see #dumpIterator(String,Iterator,PrintStream)
730    */
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);
735   }
736
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);
740   }
741   
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());
749     } else {
750       c.setMergePolicy(new MockRandomMergePolicy(r));
751     }
752     
753     if (r.nextBoolean()) {
754       c.setMergeScheduler(new SerialMergeScheduler());
755     }
756     if (r.nextBoolean()) {
757       if (rarely(r)) {
758         // crazy value
759         c.setMaxBufferedDocs(_TestUtil.nextInt(r, 2, 7));
760       } else {
761         // reasonable value
762         c.setMaxBufferedDocs(_TestUtil.nextInt(r, 8, 1000));
763       }
764     }
765     if (r.nextBoolean()) {
766       if (rarely(r)) {
767         // crazy value
768         c.setTermIndexInterval(random.nextBoolean() ? _TestUtil.nextInt(r, 1, 31) : _TestUtil.nextInt(r, 129, 1000));
769       } else {
770         // reasonable value
771         c.setTermIndexInterval(_TestUtil.nextInt(r, 32, 128));
772       }
773     }
774     if (r.nextBoolean()) {
775       c.setMaxThreadStates(_TestUtil.nextInt(r, 1, 20));
776     }
777     
778     if (r.nextBoolean()) {
779       c.setMergePolicy(new MockRandomMergePolicy(r));
780     } else {
781       c.setMergePolicy(newLogMergePolicy());
782     }
783     
784     c.setReaderPooling(r.nextBoolean());
785     c.setReaderTermsIndexDivisor(_TestUtil.nextInt(r, 1, 4));
786     return c;
787   }
788
789   public static LogMergePolicy newLogMergePolicy() {
790     return newLogMergePolicy(random);
791   }
792
793   public static TieredMergePolicy newTieredMergePolicy() {
794     return newTieredMergePolicy(random);
795   }
796
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());
801     if (rarely(r)) {
802       logmp.setMergeFactor(_TestUtil.nextInt(r, 2, 4));
803     } else {
804       logmp.setMergeFactor(_TestUtil.nextInt(r, 5, 50));
805     }
806     return logmp;
807   }
808
809   public static TieredMergePolicy newTieredMergePolicy(Random r) {
810     TieredMergePolicy tmp = new TieredMergePolicy();
811     if (rarely(r)) {
812       tmp.setMaxMergeAtOnce(_TestUtil.nextInt(r, 2, 4));
813       tmp.setMaxMergeAtOnceExplicit(_TestUtil.nextInt(r, 2, 4));
814     } else {
815       tmp.setMaxMergeAtOnce(_TestUtil.nextInt(r, 5, 50));
816       tmp.setMaxMergeAtOnceExplicit(_TestUtil.nextInt(r, 5, 50));
817     }
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);
825     return tmp;
826   }
827
828   public static LogMergePolicy newLogMergePolicy(boolean useCFS) {
829     LogMergePolicy logmp = newLogMergePolicy();
830     logmp.setUseCompoundFile(useCFS);
831     return logmp;
832   }
833
834   public static LogMergePolicy newLogMergePolicy(boolean useCFS, int mergeFactor) {
835     LogMergePolicy logmp = newLogMergePolicy();
836     logmp.setUseCompoundFile(useCFS);
837     logmp.setMergeFactor(mergeFactor);
838     return logmp;
839   }
840
841   public static LogMergePolicy newLogMergePolicy(int mergeFactor) {
842     LogMergePolicy logmp = newLogMergePolicy();
843     logmp.setMergeFactor(mergeFactor);
844     return logmp;
845   }
846
847   /**
848    * Returns a new Directory instance. Use this when the test does not
849    * care about the specific Directory implementation (most tests).
850    * <p>
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
855    * overwritten.
856    */
857   public static MockDirectoryWrapper newDirectory() throws IOException {
858     return newDirectory(random);
859   }
860   
861   /**
862    * Returns a new Directory instance, using the specified random.
863    * See {@link #newDirectory()} for more information.
864    */
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());
869     return dir;
870   }
871   
872   /**
873    * Returns a new Directory instance, with contents copied from the
874    * provided directory. See {@link #newDirectory()} for more
875    * information.
876    */
877   public static MockDirectoryWrapper newDirectory(Directory d) throws IOException {
878     return newDirectory(random, d);
879   }
880   
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);
884   }
885   
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)];
891     }
892     
893     if (fsdirClass.indexOf(".") == -1) {// if not fully qualified, assume .store
894       fsdirClass = "org.apache.lucene.store." + fsdirClass;
895     }
896     
897     Class<? extends FSDirectory> clazz;
898     try {
899       try {
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)];
904         
905         if (fsdirClass.indexOf(".") == -1) {// if not fully qualified, assume .store
906           fsdirClass = "org.apache.lucene.store." + fsdirClass;
907         }
908         
909         clazz = Class.forName(fsdirClass).asSubclass(FSDirectory.class);
910       }
911       MockDirectoryWrapper dir = new MockDirectoryWrapper(random, newFSDirectoryImpl(clazz, f));
912       if (lf != null) {
913         dir.setLockFactory(lf);
914       }
915       stores.put(dir, Thread.currentThread().getStackTrace());
916       return dir;
917     } catch (Exception e) {
918       throw new RuntimeException(e);
919     }
920   }
921   
922   /**
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.
926    */
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);
931     }
932     MockDirectoryWrapper dir = new MockDirectoryWrapper(r, impl);
933     stores.put(dir, Thread.currentThread().getStackTrace());
934     return dir;
935   }
936   
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);
941   }
942   
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);
947   }
948   
949   /**
950    * Returns a new Field instance. Use this when the test does not
951    * care about some specific field settings (most tests)
952    * <ul>
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.
956    * </ul>
957    */
958   public static Field newField(String name, String value, Store store, Index index, TermVector tv) {
959     return newField(random, name, value, store, index, tv);
960   }
961   
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);
966   }
967   
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);
972   }
973   
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);
980     }
981
982     if (!index.isIndexed())
983       return new Field(name, value, store, index, tv);
984     
985     if (!store.isStored() && random.nextBoolean())
986       store = Store.YES; // randomly store it
987     
988     tv = randomTVSetting(random, tv);
989     
990     return new Field(name, value, store, index, tv);
991   }
992   
993   static final TermVector tvSettings[] = { 
994     TermVector.NO, TermVector.YES, TermVector.WITH_OFFSETS, 
995     TermVector.WITH_POSITIONS, TermVector.WITH_POSITIONS_OFFSETS 
996   };
997   
998   private static TermVector randomTVSetting(Random random, TermVector minimum) {
999     switch(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;
1007     }
1008   }
1009   
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)];
1014   }
1015   
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)]);
1020   }
1021   
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);
1030     }
1031   }
1032
1033   private static final String FS_DIRECTORIES[] = {
1034     "SimpleFSDirectory",
1035     "NIOFSDirectory",
1036     "MMapDirectory"
1037   };
1038
1039   private static final String CORE_DIRECTORIES[] = {
1040     "RAMDirectory",
1041     FS_DIRECTORIES[0], FS_DIRECTORIES[1], FS_DIRECTORIES[2]
1042   };
1043   
1044   public static String randomDirectory(Random random) {
1045     if (rarely(random)) {
1046       return CORE_DIRECTORIES[random.nextInt(CORE_DIRECTORIES.length)];
1047     } else {
1048       return "RAMDirectory";
1049     }
1050   }
1051
1052   private static Directory newFSDirectoryImpl(
1053       Class<? extends FSDirectory> clazz, File file)
1054       throws IOException {
1055     FSDirectory d = null;
1056     try {
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);
1063     }
1064     return d;
1065   }
1066   
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());
1070   }
1071   
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;
1077     try {
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);
1082         tmpFile.delete();
1083         tmpFile.mkdir();
1084         registerTempFile(tmpFile);
1085         return newFSDirectoryImpl(clazz.asSubclass(FSDirectory.class), tmpFile);
1086       }
1087
1088       // try empty ctor
1089       return clazz.newInstance();
1090     } catch (Exception e) {
1091       throw new RuntimeException(e);
1092     } 
1093   }
1094   
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);
1099   }
1100   
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.
1105    */
1106   public static IndexSearcher newSearcher(IndexReader r, boolean maybeWrap) throws IOException {
1107     if (random.nextBoolean()) {
1108       if (maybeWrap && rarely()) {
1109         r = new SlowMultiReaderWrapper(r);
1110       }
1111       return new AssertingIndexSearcher(r);
1112     } else {
1113       int threads = 0;
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");
1119       }
1120       return new AssertingIndexSearcher(r, ex) {
1121         @Override
1122         public void close() throws IOException {
1123           super.close();
1124           shutdownExecutorService(ex);
1125         }
1126       };
1127     }
1128   }
1129   
1130   static void shutdownExecutorService(ExecutorService ex) {
1131     if (ex != null) {
1132       ex.shutdown();
1133       try {
1134         ex.awaitTermination(1000, TimeUnit.MILLISECONDS);
1135       } catch (InterruptedException e) {
1136         e.printStackTrace();
1137       }
1138     }
1139   }
1140
1141   public String getName() {
1142     return this.name;
1143   }
1144   
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()}.
1148    */
1149   
1150   protected File getDataFile(String name) throws IOException {
1151     try {
1152       return new File(this.getClass().getResource(name).toURI());
1153     } catch (Exception e) {
1154       throw new IOException("Cannot find resource: " + name);
1155     }
1156   }
1157
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());
1163   }
1164   
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();
1174   }
1175
1176   // recorded seed: for beforeClass
1177   private static long staticSeed;
1178   // seed for individual test methods, changed in @before
1179   private long seed;
1180   
1181   private static final Random seedRand = new Random();
1182   protected static final Random random = new Random(0);
1183
1184   private String name = "<unknown>";
1185   
1186   /**
1187    * Annotation for tests that should only be run during nightly builds.
1188    */
1189   @Documented
1190   @Inherited
1191   @Retention(RetentionPolicy.RUNTIME)
1192   public @interface Nightly {}
1193   
1194   /** optionally filters the tests to be run by TEST_METHOD */
1195   public static class LuceneTestCaseRunner extends BlockJUnit4ClassRunner {
1196     private List<FrameworkMethod> testMethods;
1197
1198     @Override
1199     protected List<FrameworkMethod> computeTestMethods() {
1200       if (testMethods != null)
1201         return testMethods;
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());
1209         }
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))
1217         {
1218           if (Modifier.isStatic(mod))
1219             throw new RuntimeException("Test methods must not be static.");
1220           testMethods.add(new FrameworkMethod(m));
1221         }
1222       }
1223       
1224       if (testMethods.isEmpty()) {
1225         throw new RuntimeException("No runnable methods!");
1226       }
1227       
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();
1234         } else {
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--);
1241             }
1242           }
1243         }
1244         /* dodge a possible "no-runnable methods" exception by adding a fake ignored test */
1245         if (testMethods.isEmpty()) {
1246           try {
1247             testMethods.add(new FrameworkMethod(LuceneTestCase.class.getMethod("alwaysIgnoredTestMethod")));
1248           } catch (Exception e) { throw new RuntimeException(e); }
1249         }
1250       }
1251       return testMethods;
1252     }
1253
1254     @Override
1255     protected void runChild(FrameworkMethod arg0, RunNotifier arg1) {
1256       if (VERBOSE) {
1257         System.out.println("\nNOTE: running test " + arg0.getName());
1258       }
1259       
1260       // only print iteration info if the user requested more than one iterations
1261       final boolean verbose = VERBOSE && TEST_ITER > 1;
1262       
1263       final int currentIter[] = new int[1];
1264       arg1.addListener(new RunListener() {
1265         @Override
1266         public void testFailure(Failure failure) throws Exception {
1267           if (verbose) {
1268             System.out.println("\nNOTE: iteration " + currentIter[0] + " failed! ");
1269           }
1270         }
1271       });
1272       for (int i = 0; i < TEST_ITER; i++) {
1273         currentIter[0] = i;
1274         if (verbose) {
1275           System.out.println("\nNOTE: running iter=" + (1+i) + " of " + TEST_ITER);
1276         }
1277         super.runChild(arg0, arg1);
1278         if (testsFailed) {
1279           if (i >= TEST_ITER_MIN - 1) { // XXX is this still off-by-one?
1280             break;
1281           }
1282         }
1283       }
1284     }
1285
1286     public LuceneTestCaseRunner(Class<?> clazz) throws InitializationError {
1287       super(clazz);
1288       Filter f = new Filter() {
1289
1290         @Override
1291         public String describe() { return "filters according to TEST_METHOD"; }
1292
1293         @Override
1294         public boolean shouldRun(Description d) {
1295           return TEST_METHOD == null || d.getMethodName().equals(TEST_METHOD);
1296         }     
1297       };
1298       
1299       try {
1300         f.apply(this);
1301       } catch (NoTestsRemainException e) {
1302         throw new RuntimeException(e);
1303       }
1304     }
1305   }
1306   
1307   @Ignore("just a hack")
1308   public final void alwaysIgnoredTestMethod() {}
1309 }