add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / backwards / src / test / org / apache / lucene / index / TestIndexWriterExceptions.java
1 package org.apache.lucene.index;
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.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.PrintStream;
23 import java.io.Reader;
24 import java.io.StringReader;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Random;
28
29 import org.apache.lucene.util.LuceneTestCase;
30 import org.apache.lucene.util._TestUtil;
31 import org.apache.lucene.store.Directory;
32 import org.apache.lucene.store.IndexInput;
33 import org.apache.lucene.store.IndexOutput;
34 import org.apache.lucene.store.MockDirectoryWrapper;
35 import org.apache.lucene.store.RAMDirectory;
36 import org.apache.lucene.analysis.Analyzer;
37 import org.apache.lucene.analysis.MockAnalyzer;
38 import org.apache.lucene.analysis.MockTokenizer;
39 import org.apache.lucene.analysis.TokenFilter;
40 import org.apache.lucene.analysis.TokenStream;
41 import org.apache.lucene.document.Document;
42 import org.apache.lucene.document.Field;
43 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
44 import org.apache.lucene.search.IndexSearcher;
45 import org.apache.lucene.search.PhraseQuery;
46
47 public class TestIndexWriterExceptions extends LuceneTestCase {
48
49   private class IndexerThread extends Thread {
50
51     IndexWriter writer;
52
53     final Random r = new Random(random.nextLong());
54     volatile Throwable failure;
55
56     public IndexerThread(int i, IndexWriter writer) {
57       setName("Indexer " + i);
58       this.writer = writer;
59     }
60
61     @Override
62     public void run() {
63
64       final Document doc = new Document();
65
66       doc.add(newField("content1", "aaa bbb ccc ddd", Field.Store.YES, Field.Index.ANALYZED));
67       doc.add(newField("content6", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
68       doc.add(newField("content2", "aaa bbb ccc ddd", Field.Store.YES, Field.Index.NOT_ANALYZED));
69       doc.add(newField("content3", "aaa bbb ccc ddd", Field.Store.YES, Field.Index.NO));
70
71       doc.add(newField("content4", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.ANALYZED));
72       doc.add(newField("content5", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.NOT_ANALYZED));
73
74       doc.add(newField("content7", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
75
76       final Field idField = newField("id", "", Field.Store.YES, Field.Index.NOT_ANALYZED);
77       doc.add(idField);
78
79       final long stopTime = System.currentTimeMillis() + 500;
80
81       do {
82         if (VERBOSE) {
83           System.out.println(Thread.currentThread().getName() + ": TEST: IndexerThread: cycle");
84         }
85         doFail.set(this);
86         final String id = ""+r.nextInt(50);
87         idField.setValue(id);
88         Term idTerm = new Term("id", id);
89         try {
90           if (r.nextBoolean()) {
91             final List<Document> docs = new ArrayList<Document>();
92             final int count =  _TestUtil.nextInt(r, 1, 20);
93             for(int c=0;c<count;c++) {
94               docs.add(doc);
95             }
96             writer.updateDocuments(idTerm, docs);
97           } else {
98             writer.updateDocument(idTerm, doc);
99           }
100         } catch (RuntimeException re) {
101           if (VERBOSE) {
102             System.out.println(Thread.currentThread().getName() + ": EXC: ");
103             re.printStackTrace(System.out);
104           }
105           try {
106             _TestUtil.checkIndex(writer.getDirectory());
107           } catch (IOException ioe) {
108             System.out.println(Thread.currentThread().getName() + ": unexpected exception1");
109             ioe.printStackTrace(System.out);
110             failure = ioe;
111             break;
112           }
113         } catch (Throwable t) {
114           System.out.println(Thread.currentThread().getName() + ": unexpected exception2");
115           t.printStackTrace(System.out);
116           failure = t;
117           break;
118         }
119
120         doFail.set(null);
121
122         // After a possible exception (above) I should be able
123         // to add a new document without hitting an
124         // exception:
125         try {
126           writer.updateDocument(idTerm, doc);
127         } catch (Throwable t) {
128           System.out.println(Thread.currentThread().getName() + ": unexpected exception3");
129           t.printStackTrace(System.out);
130           failure = t;
131           break;
132         }
133       } while(System.currentTimeMillis() < stopTime);
134     }
135   }
136
137   ThreadLocal<Thread> doFail = new ThreadLocal<Thread>();
138
139   private class MockIndexWriter extends IndexWriter {
140     Random r = new Random(random.nextLong());
141
142     public MockIndexWriter(Directory dir, IndexWriterConfig conf) throws IOException {
143       super(dir, conf);
144     }
145
146     @Override
147     boolean testPoint(String name) {
148       if (doFail.get() != null && !name.equals("startDoFlush") && r.nextInt(40) == 17) {
149         if (VERBOSE) {
150           System.out.println(Thread.currentThread().getName() + ": NOW FAIL: " + name);
151           new Throwable().printStackTrace(System.out);
152         }
153         throw new RuntimeException(Thread.currentThread().getName() + ": intentionally failing at " + name);
154       }
155       return true;
156     }
157   }
158
159   public void testRandomExceptions() throws Throwable {
160     if (VERBOSE) {
161       System.out.println("\nTEST: start testRandomExceptions");
162     }
163     MockDirectoryWrapper dir = newDirectory();
164
165     MockAnalyzer analyzer = new MockAnalyzer(random);
166     analyzer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
167     MockIndexWriter writer  = new MockIndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, analyzer)
168         .setRAMBufferSizeMB(0.1).setMergeScheduler(new ConcurrentMergeScheduler()));
169     ((ConcurrentMergeScheduler) writer.getConfig().getMergeScheduler()).setSuppressExceptions();
170     //writer.setMaxBufferedDocs(10);
171     if (VERBOSE) {
172       System.out.println("TEST: initial commit");
173     }
174     writer.commit();
175
176     if (VERBOSE) {
177       writer.setInfoStream(System.out);
178     }
179
180     IndexerThread thread = new IndexerThread(0, writer);
181     thread.run();
182     if (thread.failure != null) {
183       thread.failure.printStackTrace(System.out);
184       fail("thread " + thread.getName() + ": hit unexpected failure");
185     }
186
187     if (VERBOSE) {
188       System.out.println("TEST: commit after thread start");
189     }
190     writer.commit();
191
192     try {
193       writer.close();
194     } catch (Throwable t) {
195       System.out.println("exception during close:");
196       t.printStackTrace(System.out);
197       writer.rollback();
198     }
199
200     // Confirm that when doc hits exception partway through tokenization, it's deleted:
201     IndexReader r2 = IndexReader.open(dir, true);
202     final int count = r2.docFreq(new Term("content4", "aaa"));
203     final int count2 = r2.docFreq(new Term("content4", "ddd"));
204     assertEquals(count, count2);
205     r2.close();
206
207     dir.close();
208   }
209
210   public void testRandomExceptionsThreads() throws Throwable {
211     MockDirectoryWrapper dir = newDirectory();
212     MockAnalyzer analyzer = new MockAnalyzer(random);
213     analyzer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
214     MockIndexWriter writer  = new MockIndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, analyzer)
215         .setRAMBufferSizeMB(0.2).setMergeScheduler(new ConcurrentMergeScheduler()));
216     ((ConcurrentMergeScheduler) writer.getConfig().getMergeScheduler()).setSuppressExceptions();
217     //writer.setMaxBufferedDocs(10);
218     writer.commit();
219
220     if (VERBOSE) {
221       writer.setInfoStream(System.out);
222     }
223
224     final int NUM_THREADS = 4;
225
226     final IndexerThread[] threads = new IndexerThread[NUM_THREADS];
227     for(int i=0;i<NUM_THREADS;i++) {
228       threads[i] = new IndexerThread(i, writer);
229       threads[i].start();
230     }
231
232     for(int i=0;i<NUM_THREADS;i++)
233       threads[i].join();
234
235     for(int i=0;i<NUM_THREADS;i++)
236       if (threads[i].failure != null)
237         fail("thread " + threads[i].getName() + ": hit unexpected failure");
238
239     writer.commit();
240
241     try {
242       writer.close();
243     } catch (Throwable t) {
244       System.out.println("exception during close:");
245       t.printStackTrace(System.out);
246       writer.rollback();
247     }
248
249     // Confirm that when doc hits exception partway through tokenization, it's deleted:
250     IndexReader r2 = IndexReader.open(dir, true);
251     final int count = r2.docFreq(new Term("content4", "aaa"));
252     final int count2 = r2.docFreq(new Term("content4", "ddd"));
253     assertEquals(count, count2);
254     r2.close();
255
256     dir.close();
257   }
258   
259   // LUCENE-1198
260   private static final class MockIndexWriter2 extends IndexWriter {
261
262     public MockIndexWriter2(Directory dir, IndexWriterConfig conf) throws IOException {
263       super(dir, conf);
264     }
265
266     boolean doFail;
267
268     @Override
269     boolean testPoint(String name) {
270       if (doFail && name.equals("DocumentsWriter.ThreadState.init start"))
271         throw new RuntimeException("intentionally failing");
272       return true;
273     }
274   }
275   
276   private static String CRASH_FAIL_MESSAGE = "I'm experiencing problems";
277
278   private class CrashingFilter extends TokenFilter {
279     String fieldName;
280     int count;
281
282     public CrashingFilter(String fieldName, TokenStream input) {
283       super(input);
284       this.fieldName = fieldName;
285     }
286
287     @Override
288     public boolean incrementToken() throws IOException {
289       if (this.fieldName.equals("crash") && count++ >= 4)
290         throw new IOException(CRASH_FAIL_MESSAGE);
291       return input.incrementToken();
292     }
293
294     @Override
295     public void reset() throws IOException {
296       super.reset();
297       count = 0;
298     }
299   }
300
301   public void testExceptionDocumentsWriterInit() throws IOException {
302     Directory dir = newDirectory();
303     MockIndexWriter2 w = new MockIndexWriter2(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
304     w.setInfoStream(VERBOSE ? System.out : null);
305     Document doc = new Document();
306     doc.add(newField("field", "a field", Field.Store.YES,
307                       Field.Index.ANALYZED));
308     w.addDocument(doc);
309     w.doFail = true;
310     try {
311       w.addDocument(doc);
312       fail("did not hit exception");
313     } catch (RuntimeException re) {
314       // expected
315     }
316     w.close();
317     dir.close();
318   }
319
320   // LUCENE-1208
321   public void testExceptionJustBeforeFlush() throws IOException {
322     Directory dir = newDirectory();
323     MockIndexWriter w = new MockIndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMaxBufferedDocs(2));
324     w.setInfoStream(VERBOSE ? System.out : null);
325     Document doc = new Document();
326     doc.add(newField("field", "a field", Field.Store.YES,
327                       Field.Index.ANALYZED));
328     w.addDocument(doc);
329
330     Analyzer analyzer = new Analyzer() {
331       @Override
332       public TokenStream tokenStream(String fieldName, Reader reader) {
333         MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
334         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
335         return new CrashingFilter(fieldName, tokenizer);
336       }
337     };
338
339     Document crashDoc = new Document();
340     crashDoc.add(newField("crash", "do it on token 4", Field.Store.YES,
341                            Field.Index.ANALYZED));
342     try {
343       w.addDocument(crashDoc, analyzer);
344       fail("did not hit expected exception");
345     } catch (IOException ioe) {
346       // expected
347     }
348     w.addDocument(doc);
349     w.close();
350     dir.close();
351   }    
352
353   private static final class MockIndexWriter3 extends IndexWriter {
354
355     public MockIndexWriter3(Directory dir, IndexWriterConfig conf) throws IOException {
356       super(dir, conf);
357     }
358
359     boolean doFail;
360     boolean failed;
361
362     @Override
363     boolean testPoint(String name) {
364       if (doFail && name.equals("startMergeInit")) {
365         failed = true;
366         throw new RuntimeException("intentionally failing");
367       }
368       return true;
369     }
370   }
371   
372
373   // LUCENE-1210
374   public void testExceptionOnMergeInit() throws IOException {
375     Directory dir = newDirectory();
376     IndexWriterConfig conf = newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random))
377       .setMaxBufferedDocs(2).setMergeScheduler(new ConcurrentMergeScheduler()).setMergePolicy(newLogMergePolicy());
378     ((LogMergePolicy) conf.getMergePolicy()).setMergeFactor(2);
379     MockIndexWriter3 w = new MockIndexWriter3(dir, conf);
380     w.doFail = true;
381     Document doc = new Document();
382     doc.add(newField("field", "a field", Field.Store.YES,
383                       Field.Index.ANALYZED));
384     for(int i=0;i<10;i++)
385       try {
386         w.addDocument(doc);
387       } catch (RuntimeException re) {
388         break;
389       }
390
391     ((ConcurrentMergeScheduler) w.getConfig().getMergeScheduler()).sync();
392     assertTrue(w.failed);
393     w.close();
394     dir.close();
395   }
396   
397   // LUCENE-1072
398   public void testExceptionFromTokenStream() throws IOException {
399     Directory dir = newDirectory();
400     IndexWriterConfig conf = newIndexWriterConfig( TEST_VERSION_CURRENT, new Analyzer() {
401
402       @Override
403       public TokenStream tokenStream(String fieldName, Reader reader) {
404         MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.SIMPLE, true);
405         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
406         return new TokenFilter(tokenizer) {
407           private int count = 0;
408
409           @Override
410           public boolean incrementToken() throws IOException {
411             if (count++ == 5) {
412               throw new IOException();
413             }
414             return input.incrementToken();
415           }
416         };
417       }
418
419     });
420     conf.setMaxBufferedDocs(Math.max(3, conf.getMaxBufferedDocs()));
421
422     IndexWriter writer = new IndexWriter(dir, conf);
423
424     Document doc = new Document();
425     String contents = "aa bb cc dd ee ff gg hh ii jj kk";
426     doc.add(newField("content", contents, Field.Store.NO,
427         Field.Index.ANALYZED));
428     try {
429       writer.addDocument(doc);
430       fail("did not hit expected exception");
431     } catch (Exception e) {
432     }
433
434     // Make sure we can add another normal document
435     doc = new Document();
436     doc.add(newField("content", "aa bb cc dd", Field.Store.NO,
437         Field.Index.ANALYZED));
438     writer.addDocument(doc);
439
440     // Make sure we can add another normal document
441     doc = new Document();
442     doc.add(newField("content", "aa bb cc dd", Field.Store.NO,
443         Field.Index.ANALYZED));
444     writer.addDocument(doc);
445
446     writer.close();
447     IndexReader reader = IndexReader.open(dir, true);
448     final Term t = new Term("content", "aa");
449     assertEquals(3, reader.docFreq(t));
450
451     // Make sure the doc that hit the exception was marked
452     // as deleted:
453     TermDocs tdocs = reader.termDocs(t);
454     int count = 0;
455     while(tdocs.next()) {
456       count++;
457     }
458     assertEquals(2, count);
459
460     assertEquals(reader.docFreq(new Term("content", "gg")), 0);
461     reader.close();
462     dir.close();
463   }
464
465   private static class FailOnlyOnFlush extends MockDirectoryWrapper.Failure {
466     boolean doFail = false;
467     int count;
468
469     @Override
470     public void setDoFail() {
471       this.doFail = true;
472     }
473     @Override
474     public void clearDoFail() {
475       this.doFail = false;
476     }
477
478     @Override
479     public void eval(MockDirectoryWrapper dir)  throws IOException {
480       if (doFail) {
481         StackTraceElement[] trace = new Exception().getStackTrace();
482         boolean sawAppend = false;
483         boolean sawFlush = false;
484         for (int i = 0; i < trace.length; i++) {
485           if ("org.apache.lucene.index.FreqProxTermsWriter".equals(trace[i].getClassName()) && "appendPostings".equals(trace[i].getMethodName()))
486             sawAppend = true;
487           if ("doFlush".equals(trace[i].getMethodName()))
488             sawFlush = true;
489         }
490
491         if (sawAppend && sawFlush && count++ >= 30) {
492           doFail = false;
493           throw new IOException("now failing during flush");
494         }
495       }
496     }
497   }
498
499   // LUCENE-1072: make sure an errant exception on flushing
500   // one segment only takes out those docs in that one flush
501   public void testDocumentsWriterAbort() throws IOException {
502     MockDirectoryWrapper dir = newDirectory();
503     FailOnlyOnFlush failure = new FailOnlyOnFlush();
504     failure.setDoFail();
505     dir.failOn(failure);
506
507     IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMaxBufferedDocs(2));
508     Document doc = new Document();
509     String contents = "aa bb cc dd ee ff gg hh ii jj kk";
510     doc.add(newField("content", contents, Field.Store.NO,
511         Field.Index.ANALYZED));
512     boolean hitError = false;
513     for(int i=0;i<200;i++) {
514       try {
515         writer.addDocument(doc);
516       } catch (IOException ioe) {
517         // only one flush should fail:
518         assertFalse(hitError);
519         hitError = true;
520       }
521     }
522     assertTrue(hitError);
523     writer.close();
524     IndexReader reader = IndexReader.open(dir, true);
525     assertEquals(198, reader.docFreq(new Term("content", "aa")));
526     reader.close();
527     dir.close();
528   }
529
530   public void testDocumentsWriterExceptions() throws IOException {
531     Analyzer analyzer = new Analyzer() {
532       @Override
533       public TokenStream tokenStream(String fieldName, Reader reader) {
534         MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
535         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
536         return new CrashingFilter(fieldName, tokenizer);
537       }
538     };
539
540     for(int i=0;i<2;i++) {
541       if (VERBOSE) {
542         System.out.println("TEST: cycle i=" + i);
543       }
544       MockDirectoryWrapper dir = newDirectory();
545       IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, analyzer).setMergePolicy(newLogMergePolicy()));
546       writer.setInfoStream(VERBOSE ? System.out : null);
547
548       // don't allow a sudden merge to clean up the deleted
549       // doc below:
550       LogMergePolicy lmp = (LogMergePolicy) writer.getConfig().getMergePolicy();
551       lmp.setMergeFactor(Math.max(lmp.getMergeFactor(), 5));
552
553       Document doc = new Document();
554       doc.add(newField("contents", "here are some contents", Field.Store.YES,
555                         Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
556       writer.addDocument(doc);
557       writer.addDocument(doc);
558       doc.add(newField("crash", "this should crash after 4 terms", Field.Store.YES,
559                         Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
560       doc.add(newField("other", "this will not get indexed", Field.Store.YES,
561                         Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
562       try {
563         writer.addDocument(doc);
564         fail("did not hit expected exception");
565       } catch (IOException ioe) {
566         if (VERBOSE) {
567           System.out.println("TEST: hit expected exception");
568           ioe.printStackTrace(System.out);
569         }
570       }
571
572       if (0 == i) {
573         doc = new Document();
574         doc.add(newField("contents", "here are some contents", Field.Store.YES,
575                           Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
576         writer.addDocument(doc);
577         writer.addDocument(doc);
578       }
579       writer.close();
580
581       if (VERBOSE) {
582         System.out.println("TEST: open reader");
583       }
584       IndexReader reader = IndexReader.open(dir, true);
585       if (i == 0) { 
586         int expected = 5;
587         assertEquals(expected, reader.docFreq(new Term("contents", "here")));
588         assertEquals(expected, reader.maxDoc());
589         int numDel = 0;
590         for(int j=0;j<reader.maxDoc();j++) {
591           if (reader.isDeleted(j))
592             numDel++;
593           else {
594             reader.document(j);
595             reader.getTermFreqVectors(j);
596           }
597         }
598         assertEquals(1, numDel);
599       }
600       reader.close();
601
602       writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT,
603           analyzer).setMaxBufferedDocs(10));
604       doc = new Document();
605       doc.add(newField("contents", "here are some contents", Field.Store.YES,
606                         Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
607       for(int j=0;j<17;j++)
608         writer.addDocument(doc);
609       writer.optimize();
610       writer.close();
611
612       reader = IndexReader.open(dir, true);
613       int expected = 19+(1-i)*2;
614       assertEquals(expected, reader.docFreq(new Term("contents", "here")));
615       assertEquals(expected, reader.maxDoc());
616       int numDel = 0;
617       for(int j=0;j<reader.maxDoc();j++) {
618         if (reader.isDeleted(j))
619           numDel++;
620         else {
621           reader.document(j);
622           reader.getTermFreqVectors(j);
623         }
624       }
625       reader.close();
626       assertEquals(0, numDel);
627
628       dir.close();
629     }
630   }
631
632   public void testDocumentsWriterExceptionThreads() throws Exception {
633     Analyzer analyzer = new Analyzer() {
634       @Override
635       public TokenStream tokenStream(String fieldName, Reader reader) {
636         MockTokenizer tokenizer = new MockTokenizer(reader, MockTokenizer.WHITESPACE, false);
637         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
638         return new CrashingFilter(fieldName, tokenizer);
639       }
640     };
641
642     final int NUM_THREAD = 3;
643     final int NUM_ITER = 100;
644
645     for(int i=0;i<2;i++) {
646       MockDirectoryWrapper dir = newDirectory();
647
648       {
649         final IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, analyzer).setMaxBufferedDocs(-1)
650                                                    .setMergePolicy(newLogMergePolicy(10)));
651         final int finalI = i;
652
653         Thread[] threads = new Thread[NUM_THREAD];
654         for(int t=0;t<NUM_THREAD;t++) {
655           threads[t] = new Thread() {
656               @Override
657               public void run() {
658                 try {
659                   for(int iter=0;iter<NUM_ITER;iter++) {
660                     Document doc = new Document();
661                     doc.add(newField("contents", "here are some contents", Field.Store.YES,
662                                       Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
663                     writer.addDocument(doc);
664                     writer.addDocument(doc);
665                     doc.add(newField("crash", "this should crash after 4 terms", Field.Store.YES,
666                                       Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
667                     doc.add(newField("other", "this will not get indexed", Field.Store.YES,
668                                       Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
669                     try {
670                       writer.addDocument(doc);
671                       fail("did not hit expected exception");
672                     } catch (IOException ioe) {
673                     }
674
675                     if (0 == finalI) {
676                       doc = new Document();
677                       doc.add(newField("contents", "here are some contents", Field.Store.YES,
678                                         Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
679                       writer.addDocument(doc);
680                       writer.addDocument(doc);
681                     }
682                   }
683                 } catch (Throwable t) {
684                   synchronized(this) {
685                     System.out.println(Thread.currentThread().getName() + ": ERROR: hit unexpected exception");
686                     t.printStackTrace(System.out);
687                   }
688                   fail();
689                 }
690               }
691             };
692           threads[t].start();
693         }
694
695         for(int t=0;t<NUM_THREAD;t++)
696           threads[t].join();
697             
698         writer.close();
699       }
700
701       IndexReader reader = IndexReader.open(dir, true);
702       int expected = (3+(1-i)*2)*NUM_THREAD*NUM_ITER;
703       assertEquals("i=" + i, expected, reader.docFreq(new Term("contents", "here")));
704       assertEquals(expected, reader.maxDoc());
705       int numDel = 0;
706       for(int j=0;j<reader.maxDoc();j++) {
707         if (reader.isDeleted(j))
708           numDel++;
709         else {
710           reader.document(j);
711           reader.getTermFreqVectors(j);
712         }
713       }
714       reader.close();
715
716       assertEquals(NUM_THREAD*NUM_ITER, numDel);
717
718       IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(
719           TEST_VERSION_CURRENT, analyzer).setMaxBufferedDocs(10));
720       Document doc = new Document();
721       doc.add(newField("contents", "here are some contents", Field.Store.YES,
722                         Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
723       for(int j=0;j<17;j++)
724         writer.addDocument(doc);
725       writer.optimize();
726       writer.close();
727
728       reader = IndexReader.open(dir, true);
729       expected += 17-NUM_THREAD*NUM_ITER;
730       assertEquals(expected, reader.docFreq(new Term("contents", "here")));
731       assertEquals(expected, reader.maxDoc());
732       numDel = 0;
733       for(int j=0;j<reader.maxDoc();j++) {
734         if (reader.isDeleted(j))
735           numDel++;
736         else {
737           reader.document(j);
738           reader.getTermFreqVectors(j);
739         }
740       }
741       reader.close();
742
743       dir.close();
744     }
745   }
746   
747   // Throws IOException during MockDirectoryWrapper.sync
748   private static class FailOnlyInSync extends MockDirectoryWrapper.Failure {
749     boolean didFail;
750     @Override
751     public void eval(MockDirectoryWrapper dir)  throws IOException {
752       if (doFail) {
753         StackTraceElement[] trace = new Exception().getStackTrace();
754         for (int i = 0; i < trace.length; i++) {
755           if (doFail && "org.apache.lucene.store.MockDirectoryWrapper".equals(trace[i].getClassName()) && "sync".equals(trace[i].getMethodName())) {
756             didFail = true;
757             throw new IOException("now failing on purpose during sync");
758           }
759         }
760       }
761     }
762   }
763   
764   // TODO: these are also in TestIndexWriter... add a simple doc-writing method
765   // like this to LuceneTestCase?
766   private void addDoc(IndexWriter writer) throws IOException
767   {
768       Document doc = new Document();
769       doc.add(newField("content", "aaa", Field.Store.NO, Field.Index.ANALYZED));
770       writer.addDocument(doc);
771   }
772   
773   // LUCENE-1044: test exception during sync
774   public void testExceptionDuringSync() throws IOException {
775     MockDirectoryWrapper dir = newDirectory();
776     FailOnlyInSync failure = new FailOnlyInSync();
777     dir.failOn(failure);
778
779     IndexWriter writer = new IndexWriter(
780         dir,
781         newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
782             setMaxBufferedDocs(2).
783             setMergeScheduler(new ConcurrentMergeScheduler()).
784             setMergePolicy(newLogMergePolicy(5))
785     );
786     failure.setDoFail();
787     ((LogMergePolicy) writer.getConfig().getMergePolicy()).setMergeFactor(5);
788
789     for (int i = 0; i < 23; i++) {
790       addDoc(writer);
791       if ((i-1)%2 == 0) {
792         try {
793           writer.commit();
794         } catch (IOException ioe) {
795           // expected
796         }
797       }
798     }
799
800     ((ConcurrentMergeScheduler) writer.getConfig().getMergeScheduler()).sync();
801     assertTrue(failure.didFail);
802     failure.clearDoFail();
803     writer.close();
804
805     IndexReader reader = IndexReader.open(dir, true);
806     assertEquals(23, reader.numDocs());
807     reader.close();
808     dir.close();
809   }
810   
811   private static class FailOnlyInCommit extends MockDirectoryWrapper.Failure {
812
813     boolean fail1, fail2;
814
815     @Override
816     public void eval(MockDirectoryWrapper dir)  throws IOException {
817       StackTraceElement[] trace = new Exception().getStackTrace();
818       boolean isCommit = false;
819       boolean isDelete = false;
820       for (int i = 0; i < trace.length; i++) {
821         if ("org.apache.lucene.index.SegmentInfos".equals(trace[i].getClassName()) && "prepareCommit".equals(trace[i].getMethodName()))
822           isCommit = true;
823         if ("org.apache.lucene.store.MockDirectoryWrapper".equals(trace[i].getClassName()) && "deleteFile".equals(trace[i].getMethodName()))
824           isDelete = true;
825       }
826
827       if (isCommit) {
828         if (!isDelete) {
829           fail1 = true;
830           throw new RuntimeException("now fail first");
831         } else {
832           fail2 = true;
833           throw new IOException("now fail during delete");
834         }
835       }
836     }
837   }
838   
839   // LUCENE-1214
840   public void testExceptionsDuringCommit() throws Throwable {
841     MockDirectoryWrapper dir = newDirectory();
842     FailOnlyInCommit failure = new FailOnlyInCommit();
843     IndexWriter w = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
844     Document doc = new Document();
845     doc.add(newField("field", "a field", Field.Store.YES,
846                       Field.Index.ANALYZED));
847     w.addDocument(doc);
848     dir.failOn(failure);
849     try {
850       w.close();
851       fail();
852     } catch (IOException ioe) {
853       fail("expected only RuntimeException");
854     } catch (RuntimeException re) {
855       // Expected
856     }
857     assertTrue(failure.fail1 && failure.fail2);
858     w.rollback();
859     dir.close();
860   }
861   
862   public void testOptimizeExceptions() throws IOException {
863     Directory startDir = newDirectory();
864     IndexWriterConfig conf = newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMaxBufferedDocs(2).setMergePolicy(newLogMergePolicy());
865     ((LogMergePolicy) conf.getMergePolicy()).setMergeFactor(100);
866     IndexWriter w = new IndexWriter(startDir, conf);
867     for(int i=0;i<27;i++)
868       addDoc(w);
869     w.close();
870
871     int iter = TEST_NIGHTLY ? 200 : 20;
872     for(int i=0;i<iter;i++) {
873       if (VERBOSE) {
874         System.out.println("TEST: iter " + i);
875       }
876       MockDirectoryWrapper dir = new MockDirectoryWrapper(random, new RAMDirectory(startDir));
877       conf = newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMergeScheduler(new ConcurrentMergeScheduler());
878       ((ConcurrentMergeScheduler) conf.getMergeScheduler()).setSuppressExceptions();
879       w = new IndexWriter(dir, conf);
880       w.setInfoStream(VERBOSE ? System.out : null);
881       dir.setRandomIOExceptionRate(0.5);
882       try {
883         w.optimize();
884       } catch (IOException ioe) {
885         if (ioe.getCause() == null)
886           fail("optimize threw IOException without root cause");
887       }
888       dir.setRandomIOExceptionRate(0);
889       w.close();
890       dir.close();
891     }
892     startDir.close();
893   }
894   
895   // LUCENE-1429
896   public void testOutOfMemoryErrorCausesCloseToFail() throws Exception {
897
898     final List<Throwable> thrown = new ArrayList<Throwable>();
899     final Directory dir = newDirectory();
900     final IndexWriter writer = new IndexWriter(dir,
901         newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random))) {
902         @Override
903         public void message(final String message) {
904           if (message.startsWith("now flush at close") && 0 == thrown.size()) {
905             thrown.add(null);
906             throw new OutOfMemoryError("fake OOME at " + message);
907           }
908         }
909       };
910
911     // need to set an info stream so message is called
912     writer.setInfoStream(new PrintStream(new ByteArrayOutputStream()));
913     try {
914       writer.close();
915       fail("OutOfMemoryError expected");
916     }
917     catch (final OutOfMemoryError expected) {}
918
919     // throws IllegalStateEx w/o bug fix
920     writer.close();
921     dir.close();
922   }
923   
924   // LUCENE-1347
925   private static final class MockIndexWriter4 extends IndexWriter {
926
927     public MockIndexWriter4(Directory dir, IndexWriterConfig conf) throws IOException {
928       super(dir, conf);
929     }
930
931     boolean doFail;
932
933     @Override
934     boolean testPoint(String name) {
935       if (doFail && name.equals("rollback before checkpoint"))
936         throw new RuntimeException("intentionally failing");
937       return true;
938     }
939   }
940   
941   // LUCENE-1347
942   public void testRollbackExceptionHang() throws Throwable {
943     Directory dir = newDirectory();
944     MockIndexWriter4 w = new MockIndexWriter4(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
945
946     addDoc(w);
947     w.doFail = true;
948     try {
949       w.rollback();
950       fail("did not hit intentional RuntimeException");
951     } catch (RuntimeException re) {
952       // expected
953     }
954     
955     w.doFail = false;
956     w.rollback();
957     dir.close();
958   }
959   
960   // LUCENE-1044: Simulate checksum error in segments_N
961   public void testSegmentsChecksumError() throws IOException {
962     Directory dir = newDirectory();
963
964     IndexWriter writer = null;
965
966     writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
967
968     // add 100 documents
969     for (int i = 0; i < 100; i++) {
970       addDoc(writer);
971     }
972
973     // close
974     writer.close();
975
976     long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
977     assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
978
979     final String segmentsFileName = SegmentInfos.getCurrentSegmentFileName(dir);
980     IndexInput in = dir.openInput(segmentsFileName);
981     IndexOutput out = dir.createOutput(IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", 1+gen));
982     out.copyBytes(in, in.length()-1);
983     byte b = in.readByte();
984     out.writeByte((byte) (1+b));
985     out.close();
986     in.close();
987
988     IndexReader reader = null;
989     try {
990       reader = IndexReader.open(dir, true);
991     } catch (IOException e) {
992       e.printStackTrace(System.out);
993       fail("segmentInfos failed to retry fallback to correct segments_N file");
994     }
995     reader.close();
996     dir.close();
997   }
998   
999   // Simulate a corrupt index by removing last byte of
1000   // latest segments file and make sure we get an
1001   // IOException trying to open the index:
1002   public void testSimulatedCorruptIndex1() throws IOException {
1003       MockDirectoryWrapper dir = newDirectory();
1004       dir.setCheckIndexOnClose(false); // we are corrupting it!
1005
1006       IndexWriter writer = null;
1007
1008       writer  = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1009
1010       // add 100 documents
1011       for (int i = 0; i < 100; i++) {
1012           addDoc(writer);
1013       }
1014
1015       // close
1016       writer.close();
1017
1018       long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
1019       assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
1020
1021       String fileNameIn = SegmentInfos.getCurrentSegmentFileName(dir);
1022       String fileNameOut = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
1023                                                                  "",
1024                                                                  1+gen);
1025       IndexInput in = dir.openInput(fileNameIn);
1026       IndexOutput out = dir.createOutput(fileNameOut);
1027       long length = in.length();
1028       for(int i=0;i<length-1;i++) {
1029         out.writeByte(in.readByte());
1030       }
1031       in.close();
1032       out.close();
1033       dir.deleteFile(fileNameIn);
1034
1035       IndexReader reader = null;
1036       try {
1037         reader = IndexReader.open(dir, true);
1038         fail("reader did not hit IOException on opening a corrupt index");
1039       } catch (Exception e) {
1040       }
1041       if (reader != null) {
1042         reader.close();
1043       }
1044       dir.close();
1045   }
1046   
1047   // Simulate a corrupt index by removing one of the cfs
1048   // files and make sure we get an IOException trying to
1049   // open the index:
1050   public void testSimulatedCorruptIndex2() throws IOException {
1051       MockDirectoryWrapper dir = newDirectory();
1052       dir.setCheckIndexOnClose(false); // we are corrupting it!
1053       IndexWriter writer = null;
1054
1055       writer  = new IndexWriter(
1056           dir,
1057           newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
1058               setMergePolicy(newLogMergePolicy(true))
1059       );
1060       ((LogMergePolicy) writer.getConfig().getMergePolicy()).setNoCFSRatio(1.0);
1061
1062       // add 100 documents
1063       for (int i = 0; i < 100; i++) {
1064           addDoc(writer);
1065       }
1066
1067       // close
1068       writer.close();
1069
1070       long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
1071       assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
1072
1073       String[] files = dir.listAll();
1074       boolean corrupted = false;
1075       for(int i=0;i<files.length;i++) {
1076         if (files[i].endsWith(".cfs")) {
1077           dir.deleteFile(files[i]);
1078           corrupted = true;
1079           break;
1080         }
1081       }
1082       assertTrue("failed to find cfs file to remove", corrupted);
1083
1084       IndexReader reader = null;
1085       try {
1086         reader = IndexReader.open(dir, true);
1087         fail("reader did not hit IOException on opening a corrupt index");
1088       } catch (Exception e) {
1089       }
1090       if (reader != null) {
1091         reader.close();
1092       }
1093       dir.close();
1094   }
1095   
1096   // Simulate a writer that crashed while writing segments
1097   // file: make sure we can still open the index (ie,
1098   // gracefully fallback to the previous segments file),
1099   // and that we can add to the index:
1100   public void testSimulatedCrashedWriter() throws IOException {
1101       MockDirectoryWrapper dir = newDirectory();
1102       dir.setPreventDoubleWrite(false);
1103
1104       IndexWriter writer = null;
1105
1106       writer  = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1107
1108       // add 100 documents
1109       for (int i = 0; i < 100; i++) {
1110           addDoc(writer);
1111       }
1112
1113       // close
1114       writer.close();
1115
1116       long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
1117       assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
1118
1119       // Make the next segments file, with last byte
1120       // missing, to simulate a writer that crashed while
1121       // writing segments file:
1122       String fileNameIn = SegmentInfos.getCurrentSegmentFileName(dir);
1123       String fileNameOut = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
1124                                                                  "",
1125                                                                  1+gen);
1126       IndexInput in = dir.openInput(fileNameIn);
1127       IndexOutput out = dir.createOutput(fileNameOut);
1128       long length = in.length();
1129       for(int i=0;i<length-1;i++) {
1130         out.writeByte(in.readByte());
1131       }
1132       in.close();
1133       out.close();
1134
1135       IndexReader reader = null;
1136       try {
1137         reader = IndexReader.open(dir, true);
1138       } catch (Exception e) {
1139         fail("reader failed to open on a crashed index");
1140       }
1141       reader.close();
1142
1143       try {
1144         writer  = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setOpenMode(OpenMode.CREATE));
1145       } catch (Exception e) {
1146         e.printStackTrace(System.out);
1147         fail("writer failed to open on a crashed index");
1148       }
1149
1150       // add 100 documents
1151       for (int i = 0; i < 100; i++) {
1152           addDoc(writer);
1153       }
1154
1155       // close
1156       writer.close();
1157       dir.close();
1158   }
1159
1160   public void testAddDocsNonAbortingException() throws Exception {
1161     final Directory dir = newDirectory();
1162     final RandomIndexWriter w = new RandomIndexWriter(random, dir);
1163     final int numDocs1 = random.nextInt(25);
1164     for(int docCount=0;docCount<numDocs1;docCount++) {
1165       Document doc = new Document();
1166       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1167       w.addDocument(doc);
1168     }
1169     
1170     final List<Document> docs = new ArrayList<Document>();
1171     for(int docCount=0;docCount<7;docCount++) {
1172       Document doc = new Document();
1173       docs.add(doc);
1174       doc.add(newField("id", docCount+"", Field.Index.NOT_ANALYZED));
1175       doc.add(newField("content", "silly content " + docCount, Field.Index.ANALYZED));
1176       if (docCount == 4) {
1177         Field f = newField("crash", "", Field.Index.ANALYZED);
1178         doc.add(f);
1179         MockTokenizer tokenizer = new MockTokenizer(new StringReader("crash me on the 4th token"), MockTokenizer.WHITESPACE, false);
1180         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
1181         f.setTokenStream(new CrashingFilter("crash", tokenizer));
1182       }
1183     }
1184     try {
1185       w.addDocuments(docs);
1186       // BUG: CrashingFilter didn't
1187       fail("did not hit expected exception");
1188     } catch (IOException ioe) {
1189       // expected
1190       assertEquals(CRASH_FAIL_MESSAGE, ioe.getMessage());
1191     }
1192
1193     final int numDocs2 = random.nextInt(25);
1194     for(int docCount=0;docCount<numDocs2;docCount++) {
1195       Document doc = new Document();
1196       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1197       w.addDocument(doc);
1198     }
1199
1200     final IndexReader r = w.getReader();
1201     w.close();
1202
1203     final IndexSearcher s = new IndexSearcher(r);
1204     PhraseQuery pq = new PhraseQuery();
1205     pq.add(new Term("content", "silly"));
1206     pq.add(new Term("content", "content"));
1207     assertEquals(0, s.search(pq, 1).totalHits);
1208
1209     pq = new PhraseQuery();
1210     pq.add(new Term("content", "good"));
1211     pq.add(new Term("content", "content"));
1212     assertEquals(numDocs1+numDocs2, s.search(pq, 1).totalHits);
1213     r.close();
1214     dir.close();
1215   }
1216
1217
1218   public void testUpdateDocsNonAbortingException() throws Exception {
1219     final Directory dir = newDirectory();
1220     final RandomIndexWriter w = new RandomIndexWriter(random, dir);
1221     final int numDocs1 = random.nextInt(25);
1222     for(int docCount=0;docCount<numDocs1;docCount++) {
1223       Document doc = new Document();
1224       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1225       w.addDocument(doc);
1226     }
1227
1228     // Use addDocs (no exception) to get docs in the index:
1229     final List<Document> docs = new ArrayList<Document>();
1230     final int numDocs2 = random.nextInt(25);
1231     for(int docCount=0;docCount<numDocs2;docCount++) {
1232       Document doc = new Document();
1233       docs.add(doc);
1234       doc.add(newField("subid", "subs", Field.Index.NOT_ANALYZED));
1235       doc.add(newField("id", docCount+"", Field.Index.NOT_ANALYZED));
1236       doc.add(newField("content", "silly content " + docCount, Field.Index.ANALYZED));
1237     }
1238     w.addDocuments(docs);
1239
1240     final int numDocs3 = random.nextInt(25);
1241     for(int docCount=0;docCount<numDocs3;docCount++) {
1242       Document doc = new Document();
1243       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1244       w.addDocument(doc);
1245     }
1246
1247     docs.clear();
1248     final int limit = _TestUtil.nextInt(random, 2, 25);
1249     final int crashAt = random.nextInt(limit);
1250     for(int docCount=0;docCount<limit;docCount++) {
1251       Document doc = new Document();
1252       docs.add(doc);
1253       doc.add(newField("id", docCount+"", Field.Index.NOT_ANALYZED));
1254       doc.add(newField("content", "silly content " + docCount, Field.Index.ANALYZED));
1255       if (docCount == crashAt) {
1256         Field f = newField("crash", "", Field.Index.ANALYZED);
1257         doc.add(f);
1258         MockTokenizer tokenizer = new MockTokenizer(new StringReader("crash me on the 4th token"), MockTokenizer.WHITESPACE, false);
1259         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
1260         f.setTokenStream(new CrashingFilter("crash", tokenizer));
1261       }
1262     }
1263
1264     try {
1265       w.updateDocuments(new Term("subid", "subs"), docs);
1266       // BUG: CrashingFilter didn't
1267       fail("did not hit expected exception");
1268     } catch (IOException ioe) {
1269       // expected
1270       assertEquals(CRASH_FAIL_MESSAGE, ioe.getMessage());
1271     }
1272
1273     final int numDocs4 = random.nextInt(25);
1274     for(int docCount=0;docCount<numDocs4;docCount++) {
1275       Document doc = new Document();
1276       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1277       w.addDocument(doc);
1278     }
1279
1280     final IndexReader r = w.getReader();
1281     w.close();
1282
1283     final IndexSearcher s = new IndexSearcher(r);
1284     PhraseQuery pq = new PhraseQuery();
1285     pq.add(new Term("content", "silly"));
1286     pq.add(new Term("content", "content"));
1287     assertEquals(numDocs2, s.search(pq, 1).totalHits);
1288
1289     pq = new PhraseQuery();
1290     pq.add(new Term("content", "good"));
1291     pq.add(new Term("content", "content"));
1292     assertEquals(numDocs1+numDocs3+numDocs4, s.search(pq, 1).totalHits);
1293     r.close();
1294     dir.close();
1295   }
1296 }