pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / 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.analysis.Analyzer;
30 import org.apache.lucene.analysis.MockAnalyzer;
31 import org.apache.lucene.analysis.MockTokenizer;
32 import org.apache.lucene.analysis.TokenFilter;
33 import org.apache.lucene.analysis.TokenStream;
34 import org.apache.lucene.document.Document;
35 import org.apache.lucene.document.Field;
36 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
37 import org.apache.lucene.search.IndexSearcher;
38 import org.apache.lucene.search.PhraseQuery;
39 import org.apache.lucene.store.Directory;
40 import org.apache.lucene.store.IndexInput;
41 import org.apache.lucene.store.IndexOutput;
42 import org.apache.lucene.store.MockDirectoryWrapper;
43 import org.apache.lucene.store.RAMDirectory;
44 import org.apache.lucene.util.LuceneTestCase;
45 import org.apache.lucene.util._TestUtil;
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(r, "content1", "aaa bbb ccc ddd", Field.Store.YES, Field.Index.ANALYZED));
67       doc.add(newField(r, "content6", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
68       doc.add(newField(r, "content2", "aaa bbb ccc ddd", Field.Store.YES, Field.Index.NOT_ANALYZED));
69       doc.add(newField(r, "content3", "aaa bbb ccc ddd", Field.Store.YES, Field.Index.NO));
70
71       doc.add(newField(r, "content4", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.ANALYZED));
72       doc.add(newField(r, "content5", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.NOT_ANALYZED));
73
74       doc.add(newField(r, "content7", "aaa bbb ccc ddd", Field.Store.NO, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
75
76       final Field idField = newField(r, "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.forceMerge(1);
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.forceMerge(1);
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     dir.setFailOnCreateOutput(false);
843     FailOnlyInCommit failure = new FailOnlyInCommit();
844     IndexWriter w = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
845     Document doc = new Document();
846     doc.add(newField("field", "a field", Field.Store.YES,
847                       Field.Index.ANALYZED));
848     w.addDocument(doc);
849     dir.failOn(failure);
850     try {
851       w.close();
852       fail();
853     } catch (IOException ioe) {
854       fail("expected only RuntimeException");
855     } catch (RuntimeException re) {
856       // Expected
857     }
858     assertTrue(failure.fail1 && failure.fail2);
859     w.rollback();
860     dir.close();
861   }
862   
863   public void testForceMergeExceptions() throws IOException {
864     Directory startDir = newDirectory();
865     IndexWriterConfig conf = newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMaxBufferedDocs(2).setMergePolicy(newLogMergePolicy());
866     ((LogMergePolicy) conf.getMergePolicy()).setMergeFactor(100);
867     IndexWriter w = new IndexWriter(startDir, conf);
868     for(int i=0;i<27;i++)
869       addDoc(w);
870     w.close();
871
872     int iter = TEST_NIGHTLY ? 200 : 20;
873     for(int i=0;i<iter;i++) {
874       if (VERBOSE) {
875         System.out.println("TEST: iter " + i);
876       }
877       MockDirectoryWrapper dir = new MockDirectoryWrapper(random, new RAMDirectory(startDir));
878       conf = newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setMergeScheduler(new ConcurrentMergeScheduler());
879       ((ConcurrentMergeScheduler) conf.getMergeScheduler()).setSuppressExceptions();
880       w = new IndexWriter(dir, conf);
881       w.setInfoStream(VERBOSE ? System.out : null);
882       dir.setRandomIOExceptionRate(0.5);
883       try {
884         w.forceMerge(1);
885       } catch (IOException ioe) {
886         if (ioe.getCause() == null)
887           fail("forceMerge threw IOException without root cause");
888       }
889       dir.setRandomIOExceptionRate(0);
890       w.close();
891       dir.close();
892     }
893     startDir.close();
894   }
895   
896   // LUCENE-1429
897   public void testOutOfMemoryErrorCausesCloseToFail() throws Exception {
898
899     final List<Throwable> thrown = new ArrayList<Throwable>();
900     final Directory dir = newDirectory();
901     final IndexWriter writer = new IndexWriter(dir,
902         newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random))) {
903         @Override
904         public void message(final String message) {
905           if (message.startsWith("now flush at close") && 0 == thrown.size()) {
906             thrown.add(null);
907             throw new OutOfMemoryError("fake OOME at " + message);
908           }
909         }
910       };
911
912     // need to set an info stream so message is called
913     writer.setInfoStream(new PrintStream(new ByteArrayOutputStream()));
914     try {
915       writer.close();
916       fail("OutOfMemoryError expected");
917     }
918     catch (final OutOfMemoryError expected) {}
919
920     // throws IllegalStateEx w/o bug fix
921     writer.close();
922     dir.close();
923   }
924   
925   // LUCENE-1347
926   private static final class MockIndexWriter4 extends IndexWriter {
927
928     public MockIndexWriter4(Directory dir, IndexWriterConfig conf) throws IOException {
929       super(dir, conf);
930     }
931
932     boolean doFail;
933
934     @Override
935     boolean testPoint(String name) {
936       if (doFail && name.equals("rollback before checkpoint"))
937         throw new RuntimeException("intentionally failing");
938       return true;
939     }
940   }
941   
942   // LUCENE-1347
943   public void testRollbackExceptionHang() throws Throwable {
944     Directory dir = newDirectory();
945     MockIndexWriter4 w = new MockIndexWriter4(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
946
947     addDoc(w);
948     w.doFail = true;
949     try {
950       w.rollback();
951       fail("did not hit intentional RuntimeException");
952     } catch (RuntimeException re) {
953       // expected
954     }
955     
956     w.doFail = false;
957     w.rollback();
958     dir.close();
959   }
960   
961   // LUCENE-1044: Simulate checksum error in segments_N
962   public void testSegmentsChecksumError() throws IOException {
963     Directory dir = newDirectory();
964
965     IndexWriter writer = null;
966
967     writer = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
968
969     // add 100 documents
970     for (int i = 0; i < 100; i++) {
971       addDoc(writer);
972     }
973
974     // close
975     writer.close();
976
977     long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
978     assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
979
980     final String segmentsFileName = SegmentInfos.getCurrentSegmentFileName(dir);
981     IndexInput in = dir.openInput(segmentsFileName);
982     IndexOutput out = dir.createOutput(IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", 1+gen));
983     out.copyBytes(in, in.length()-1);
984     byte b = in.readByte();
985     out.writeByte((byte) (1+b));
986     out.close();
987     in.close();
988
989     IndexReader reader = null;
990     try {
991       reader = IndexReader.open(dir, true);
992     } catch (IOException e) {
993       e.printStackTrace(System.out);
994       fail("segmentInfos failed to retry fallback to correct segments_N file");
995     }
996     reader.close();
997     dir.close();
998   }
999   
1000   // Simulate a corrupt index by removing last byte of
1001   // latest segments file and make sure we get an
1002   // IOException trying to open the index:
1003   public void testSimulatedCorruptIndex1() throws IOException {
1004       MockDirectoryWrapper dir = newDirectory();
1005       dir.setCheckIndexOnClose(false); // we are corrupting it!
1006
1007       IndexWriter writer = null;
1008
1009       writer  = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1010
1011       // add 100 documents
1012       for (int i = 0; i < 100; i++) {
1013           addDoc(writer);
1014       }
1015
1016       // close
1017       writer.close();
1018
1019       long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
1020       assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
1021
1022       String fileNameIn = SegmentInfos.getCurrentSegmentFileName(dir);
1023       String fileNameOut = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
1024                                                                  "",
1025                                                                  1+gen);
1026       IndexInput in = dir.openInput(fileNameIn);
1027       IndexOutput out = dir.createOutput(fileNameOut);
1028       long length = in.length();
1029       for(int i=0;i<length-1;i++) {
1030         out.writeByte(in.readByte());
1031       }
1032       in.close();
1033       out.close();
1034       dir.deleteFile(fileNameIn);
1035
1036       IndexReader reader = null;
1037       try {
1038         reader = IndexReader.open(dir, true);
1039         fail("reader did not hit IOException on opening a corrupt index");
1040       } catch (Exception e) {
1041       }
1042       if (reader != null) {
1043         reader.close();
1044       }
1045       dir.close();
1046   }
1047   
1048   // Simulate a corrupt index by removing one of the cfs
1049   // files and make sure we get an IOException trying to
1050   // open the index:
1051   public void testSimulatedCorruptIndex2() throws IOException {
1052       MockDirectoryWrapper dir = newDirectory();
1053       dir.setCheckIndexOnClose(false); // we are corrupting it!
1054       IndexWriter writer = null;
1055
1056       writer  = new IndexWriter(
1057           dir,
1058           newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random)).
1059               setMergePolicy(newLogMergePolicy(true))
1060       );
1061       ((LogMergePolicy) writer.getConfig().getMergePolicy()).setNoCFSRatio(1.0);
1062
1063       // add 100 documents
1064       for (int i = 0; i < 100; i++) {
1065           addDoc(writer);
1066       }
1067
1068       // close
1069       writer.close();
1070
1071       long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
1072       assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
1073
1074       String[] files = dir.listAll();
1075       boolean corrupted = false;
1076       for(int i=0;i<files.length;i++) {
1077         if (files[i].endsWith(".cfs")) {
1078           dir.deleteFile(files[i]);
1079           corrupted = true;
1080           break;
1081         }
1082       }
1083       assertTrue("failed to find cfs file to remove", corrupted);
1084
1085       IndexReader reader = null;
1086       try {
1087         reader = IndexReader.open(dir, true);
1088         fail("reader did not hit IOException on opening a corrupt index");
1089       } catch (Exception e) {
1090       }
1091       if (reader != null) {
1092         reader.close();
1093       }
1094       dir.close();
1095   }
1096   
1097   // Simulate a writer that crashed while writing segments
1098   // file: make sure we can still open the index (ie,
1099   // gracefully fallback to the previous segments file),
1100   // and that we can add to the index:
1101   public void testSimulatedCrashedWriter() throws IOException {
1102       MockDirectoryWrapper dir = newDirectory();
1103       dir.setPreventDoubleWrite(false);
1104
1105       IndexWriter writer = null;
1106
1107       writer  = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)));
1108
1109       // add 100 documents
1110       for (int i = 0; i < 100; i++) {
1111           addDoc(writer);
1112       }
1113
1114       // close
1115       writer.close();
1116
1117       long gen = SegmentInfos.getCurrentSegmentGeneration(dir);
1118       assertTrue("segment generation should be > 0 but got " + gen, gen > 0);
1119
1120       // Make the next segments file, with last byte
1121       // missing, to simulate a writer that crashed while
1122       // writing segments file:
1123       String fileNameIn = SegmentInfos.getCurrentSegmentFileName(dir);
1124       String fileNameOut = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS,
1125                                                                  "",
1126                                                                  1+gen);
1127       IndexInput in = dir.openInput(fileNameIn);
1128       IndexOutput out = dir.createOutput(fileNameOut);
1129       long length = in.length();
1130       for(int i=0;i<length-1;i++) {
1131         out.writeByte(in.readByte());
1132       }
1133       in.close();
1134       out.close();
1135
1136       IndexReader reader = null;
1137       try {
1138         reader = IndexReader.open(dir, true);
1139       } catch (Exception e) {
1140         fail("reader failed to open on a crashed index");
1141       }
1142       reader.close();
1143
1144       try {
1145         writer  = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setOpenMode(OpenMode.CREATE));
1146       } catch (Exception e) {
1147         e.printStackTrace(System.out);
1148         fail("writer failed to open on a crashed index");
1149       }
1150
1151       // add 100 documents
1152       for (int i = 0; i < 100; i++) {
1153           addDoc(writer);
1154       }
1155
1156       // close
1157       writer.close();
1158       dir.close();
1159   }
1160
1161   public void testAddDocsNonAbortingException() throws Exception {
1162     final Directory dir = newDirectory();
1163     final RandomIndexWriter w = new RandomIndexWriter(random, dir);
1164     final int numDocs1 = random.nextInt(25);
1165     for(int docCount=0;docCount<numDocs1;docCount++) {
1166       Document doc = new Document();
1167       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1168       w.addDocument(doc);
1169     }
1170     
1171     final List<Document> docs = new ArrayList<Document>();
1172     for(int docCount=0;docCount<7;docCount++) {
1173       Document doc = new Document();
1174       docs.add(doc);
1175       doc.add(newField("id", docCount+"", Field.Index.NOT_ANALYZED));
1176       doc.add(newField("content", "silly content " + docCount, Field.Index.ANALYZED));
1177       if (docCount == 4) {
1178         Field f = newField("crash", "", Field.Index.ANALYZED);
1179         doc.add(f);
1180         MockTokenizer tokenizer = new MockTokenizer(new StringReader("crash me on the 4th token"), MockTokenizer.WHITESPACE, false);
1181         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
1182         f.setTokenStream(new CrashingFilter("crash", tokenizer));
1183       }
1184     }
1185     try {
1186       w.addDocuments(docs);
1187       // BUG: CrashingFilter didn't
1188       fail("did not hit expected exception");
1189     } catch (IOException ioe) {
1190       // expected
1191       assertEquals(CRASH_FAIL_MESSAGE, ioe.getMessage());
1192     }
1193
1194     final int numDocs2 = random.nextInt(25);
1195     for(int docCount=0;docCount<numDocs2;docCount++) {
1196       Document doc = new Document();
1197       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1198       w.addDocument(doc);
1199     }
1200
1201     final IndexReader r = w.getReader();
1202     w.close();
1203
1204     final IndexSearcher s = new IndexSearcher(r);
1205     PhraseQuery pq = new PhraseQuery();
1206     pq.add(new Term("content", "silly"));
1207     pq.add(new Term("content", "content"));
1208     assertEquals(0, s.search(pq, 1).totalHits);
1209
1210     pq = new PhraseQuery();
1211     pq.add(new Term("content", "good"));
1212     pq.add(new Term("content", "content"));
1213     assertEquals(numDocs1+numDocs2, s.search(pq, 1).totalHits);
1214     r.close();
1215     dir.close();
1216   }
1217
1218
1219   public void testUpdateDocsNonAbortingException() throws Exception {
1220     final Directory dir = newDirectory();
1221     final RandomIndexWriter w = new RandomIndexWriter(random, dir);
1222     final int numDocs1 = random.nextInt(25);
1223     for(int docCount=0;docCount<numDocs1;docCount++) {
1224       Document doc = new Document();
1225       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1226       w.addDocument(doc);
1227     }
1228
1229     // Use addDocs (no exception) to get docs in the index:
1230     final List<Document> docs = new ArrayList<Document>();
1231     final int numDocs2 = random.nextInt(25);
1232     for(int docCount=0;docCount<numDocs2;docCount++) {
1233       Document doc = new Document();
1234       docs.add(doc);
1235       doc.add(newField("subid", "subs", Field.Index.NOT_ANALYZED));
1236       doc.add(newField("id", docCount+"", Field.Index.NOT_ANALYZED));
1237       doc.add(newField("content", "silly content " + docCount, Field.Index.ANALYZED));
1238     }
1239     w.addDocuments(docs);
1240
1241     final int numDocs3 = random.nextInt(25);
1242     for(int docCount=0;docCount<numDocs3;docCount++) {
1243       Document doc = new Document();
1244       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1245       w.addDocument(doc);
1246     }
1247
1248     docs.clear();
1249     final int limit = _TestUtil.nextInt(random, 2, 25);
1250     final int crashAt = random.nextInt(limit);
1251     for(int docCount=0;docCount<limit;docCount++) {
1252       Document doc = new Document();
1253       docs.add(doc);
1254       doc.add(newField("id", docCount+"", Field.Index.NOT_ANALYZED));
1255       doc.add(newField("content", "silly content " + docCount, Field.Index.ANALYZED));
1256       if (docCount == crashAt) {
1257         Field f = newField("crash", "", Field.Index.ANALYZED);
1258         doc.add(f);
1259         MockTokenizer tokenizer = new MockTokenizer(new StringReader("crash me on the 4th token"), MockTokenizer.WHITESPACE, false);
1260         tokenizer.setEnableChecks(false); // disable workflow checking as we forcefully close() in exceptional cases.
1261         f.setTokenStream(new CrashingFilter("crash", tokenizer));
1262       }
1263     }
1264
1265     try {
1266       w.updateDocuments(new Term("subid", "subs"), docs);
1267       // BUG: CrashingFilter didn't
1268       fail("did not hit expected exception");
1269     } catch (IOException ioe) {
1270       // expected
1271       assertEquals(CRASH_FAIL_MESSAGE, ioe.getMessage());
1272     }
1273
1274     final int numDocs4 = random.nextInt(25);
1275     for(int docCount=0;docCount<numDocs4;docCount++) {
1276       Document doc = new Document();
1277       doc.add(newField("content", "good content", Field.Index.ANALYZED));
1278       w.addDocument(doc);
1279     }
1280
1281     final IndexReader r = w.getReader();
1282     w.close();
1283
1284     final IndexSearcher s = new IndexSearcher(r);
1285     PhraseQuery pq = new PhraseQuery();
1286     pq.add(new Term("content", "silly"));
1287     pq.add(new Term("content", "content"));
1288     assertEquals(numDocs2, s.search(pq, 1).totalHits);
1289
1290     pq = new PhraseQuery();
1291     pq.add(new Term("content", "good"));
1292     pq.add(new Term("content", "content"));
1293     assertEquals(numDocs1+numDocs3+numDocs4, s.search(pq, 1).totalHits);
1294     r.close();
1295     dir.close();
1296   }
1297 }