add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / backwards / src / test / org / apache / lucene / index / TestIndexWriterDelete.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.IOException;
21 import java.io.Reader;
22 import java.util.Arrays;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Random;
27
28 import org.apache.lucene.analysis.Analyzer;
29 import org.apache.lucene.analysis.MockAnalyzer;
30 import org.apache.lucene.analysis.MockTokenizer;
31 import org.apache.lucene.analysis.TokenStream;
32 import org.apache.lucene.document.Document;
33 import org.apache.lucene.document.Field;
34 import org.apache.lucene.document.Field.Index;
35 import org.apache.lucene.document.Field.Store;
36 import org.apache.lucene.document.Field.TermVector;
37 import org.apache.lucene.search.IndexSearcher;
38 import org.apache.lucene.search.ScoreDoc;
39 import org.apache.lucene.search.TermQuery;
40 import org.apache.lucene.store.Directory;
41 import org.apache.lucene.store.MockDirectoryWrapper;
42 import org.apache.lucene.store.RAMDirectory;
43 import org.apache.lucene.util.LuceneTestCase;
44 import org.apache.lucene.util._TestUtil;
45
46 public class TestIndexWriterDelete extends LuceneTestCase {
47   
48   // test the simple case
49   public void testSimpleCase() throws IOException {
50     String[] keywords = { "1", "2" };
51     String[] unindexed = { "Netherlands", "Italy" };
52     String[] unstored = { "Amsterdam has lots of bridges",
53         "Venice has lots of canals" };
54     String[] text = { "Amsterdam", "Venice" };
55
56     Directory dir = newDirectory();
57     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
58         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDeleteTerms(1));
59
60     for (int i = 0; i < keywords.length; i++) {
61       Document doc = new Document();
62       doc.add(newField("id", keywords[i], Field.Store.YES,
63                         Field.Index.NOT_ANALYZED));
64       doc.add(newField("country", unindexed[i], Field.Store.YES,
65                         Field.Index.NO));
66       doc.add(newField("contents", unstored[i], Field.Store.NO,
67                         Field.Index.ANALYZED));
68       doc
69         .add(newField("city", text[i], Field.Store.YES,
70                        Field.Index.ANALYZED));
71       modifier.addDocument(doc);
72     }
73     modifier.optimize();
74     modifier.commit();
75
76     Term term = new Term("city", "Amsterdam");
77     int hitCount = getHitCount(dir, term);
78     assertEquals(1, hitCount);
79     modifier.deleteDocuments(term);
80     modifier.commit();
81     hitCount = getHitCount(dir, term);
82     assertEquals(0, hitCount);
83
84     modifier.close();
85     dir.close();
86   }
87
88   // test when delete terms only apply to disk segments
89   public void testNonRAMDelete() throws IOException {
90
91     Directory dir = newDirectory();
92     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
93         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
94         .setMaxBufferedDeleteTerms(2));
95     modifier.setInfoStream(VERBOSE ? System.out : null);
96     int id = 0;
97     int value = 100;
98
99     for (int i = 0; i < 7; i++) {
100       addDoc(modifier, ++id, value);
101     }
102     modifier.commit();
103
104     assertEquals(0, modifier.getNumBufferedDocuments());
105     assertTrue(0 < modifier.getSegmentCount());
106
107     modifier.commit();
108
109     IndexReader reader = IndexReader.open(dir, true);
110     assertEquals(7, reader.numDocs());
111     reader.close();
112
113     modifier.deleteDocuments(new Term("value", String.valueOf(value)));
114
115     modifier.commit();
116
117     reader = IndexReader.open(dir, true);
118     assertEquals(0, reader.numDocs());
119     reader.close();
120     modifier.close();
121     dir.close();
122   }
123
124   public void testMaxBufferedDeletes() throws IOException {
125     Directory dir = newDirectory();
126     IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(
127         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDeleteTerms(1));
128
129     writer.setInfoStream(VERBOSE ? System.out : null);
130     writer.addDocument(new Document());
131     writer.deleteDocuments(new Term("foobar", "1"));
132     writer.deleteDocuments(new Term("foobar", "1"));
133     writer.deleteDocuments(new Term("foobar", "1"));
134     assertEquals(3, writer.getFlushDeletesCount());
135     writer.close();
136     dir.close();
137   }
138
139   // test when delete terms only apply to ram segments
140   public void testRAMDeletes() throws IOException {
141     for(int t=0;t<2;t++) {
142       if (VERBOSE) {
143         System.out.println("TEST: t=" + t);
144       }
145       Directory dir = newDirectory();
146       IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
147           TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(4)
148           .setMaxBufferedDeleteTerms(4));
149       modifier.setInfoStream(VERBOSE ? System.out : null);
150       int id = 0;
151       int value = 100;
152
153       addDoc(modifier, ++id, value);
154       if (0 == t)
155         modifier.deleteDocuments(new Term("value", String.valueOf(value)));
156       else
157         modifier.deleteDocuments(new TermQuery(new Term("value", String.valueOf(value))));
158       addDoc(modifier, ++id, value);
159       if (0 == t) {
160         modifier.deleteDocuments(new Term("value", String.valueOf(value)));
161         assertEquals(2, modifier.getNumBufferedDeleteTerms());
162         assertEquals(1, modifier.getBufferedDeleteTermsSize());
163       }
164       else
165         modifier.deleteDocuments(new TermQuery(new Term("value", String.valueOf(value))));
166
167       addDoc(modifier, ++id, value);
168       assertEquals(0, modifier.getSegmentCount());
169       modifier.commit();
170
171       IndexReader reader = IndexReader.open(dir, true);
172       assertEquals(1, reader.numDocs());
173
174       int hitCount = getHitCount(dir, new Term("id", String.valueOf(id)));
175       assertEquals(1, hitCount);
176       reader.close();
177       modifier.close();
178       dir.close();
179     }
180   }
181
182   // test when delete terms apply to both disk and ram segments
183   public void testBothDeletes() throws IOException {
184     Directory dir = newDirectory();
185     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
186         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(100)
187         .setMaxBufferedDeleteTerms(100));
188
189     int id = 0;
190     int value = 100;
191
192     for (int i = 0; i < 5; i++) {
193       addDoc(modifier, ++id, value);
194     }
195
196     value = 200;
197     for (int i = 0; i < 5; i++) {
198       addDoc(modifier, ++id, value);
199     }
200     modifier.commit();
201
202     for (int i = 0; i < 5; i++) {
203       addDoc(modifier, ++id, value);
204     }
205     modifier.deleteDocuments(new Term("value", String.valueOf(value)));
206
207     modifier.commit();
208
209     IndexReader reader = IndexReader.open(dir, true);
210     assertEquals(5, reader.numDocs());
211     modifier.close();
212     reader.close();
213     dir.close();
214   }
215
216   // test that batched delete terms are flushed together
217   public void testBatchDeletes() throws IOException {
218     Directory dir = newDirectory();
219     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
220         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
221         .setMaxBufferedDeleteTerms(2));
222
223     int id = 0;
224     int value = 100;
225
226     for (int i = 0; i < 7; i++) {
227       addDoc(modifier, ++id, value);
228     }
229     modifier.commit();
230
231     IndexReader reader = IndexReader.open(dir, true);
232     assertEquals(7, reader.numDocs());
233     reader.close();
234       
235     id = 0;
236     modifier.deleteDocuments(new Term("id", String.valueOf(++id)));
237     modifier.deleteDocuments(new Term("id", String.valueOf(++id)));
238
239     modifier.commit();
240
241     reader = IndexReader.open(dir, true);
242     assertEquals(5, reader.numDocs());
243     reader.close();
244
245     Term[] terms = new Term[3];
246     for (int i = 0; i < terms.length; i++) {
247       terms[i] = new Term("id", String.valueOf(++id));
248     }
249     modifier.deleteDocuments(terms);
250     modifier.commit();
251     reader = IndexReader.open(dir, true);
252     assertEquals(2, reader.numDocs());
253     reader.close();
254
255     modifier.close();
256     dir.close();
257   }
258
259   // test deleteAll()
260   public void testDeleteAll() throws IOException {
261     Directory dir = newDirectory();
262     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
263         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
264         .setMaxBufferedDeleteTerms(2));
265
266     int id = 0;
267     int value = 100;
268
269     for (int i = 0; i < 7; i++) {
270       addDoc(modifier, ++id, value);
271     }
272     modifier.commit();
273
274     IndexReader reader = IndexReader.open(dir, true);
275     assertEquals(7, reader.numDocs());
276     reader.close();
277
278     // Add 1 doc (so we will have something buffered)
279     addDoc(modifier, 99, value);
280
281     // Delete all
282     modifier.deleteAll();
283
284     // Delete all shouldn't be on disk yet
285     reader = IndexReader.open(dir, true);
286     assertEquals(7, reader.numDocs());
287     reader.close();
288
289     // Add a doc and update a doc (after the deleteAll, before the commit)
290     addDoc(modifier, 101, value);
291     updateDoc(modifier, 102, value);
292
293     // commit the delete all
294     modifier.commit();
295
296     // Validate there are no docs left
297     reader = IndexReader.open(dir, true);
298     assertEquals(2, reader.numDocs());
299     reader.close();
300
301     modifier.close();
302     dir.close();
303   }
304
305   // test rollback of deleteAll()
306   public void testDeleteAllRollback() throws IOException {
307     Directory dir = newDirectory();
308     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
309         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
310         .setMaxBufferedDeleteTerms(2));
311     
312     int id = 0;
313     int value = 100;
314     
315     for (int i = 0; i < 7; i++) {
316       addDoc(modifier, ++id, value);
317     }
318     modifier.commit();
319     
320     addDoc(modifier, ++id, value);
321
322     IndexReader reader = IndexReader.open(dir, true);
323     assertEquals(7, reader.numDocs());
324     reader.close();
325     
326     // Delete all
327     modifier.deleteAll(); 
328
329     // Roll it back
330     modifier.rollback();
331     modifier.close();
332     
333     // Validate that the docs are still there
334     reader = IndexReader.open(dir, true);
335     assertEquals(7, reader.numDocs());
336     reader.close();
337     
338     dir.close();
339   }
340
341
342   // test deleteAll() w/ near real-time reader
343   public void testDeleteAllNRT() throws IOException {
344     Directory dir = newDirectory();
345     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
346         TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDocs(2)
347         .setMaxBufferedDeleteTerms(2));
348     
349     int id = 0;
350     int value = 100;
351     
352     for (int i = 0; i < 7; i++) {
353       addDoc(modifier, ++id, value);
354     }
355     modifier.commit();
356
357     IndexReader reader = modifier.getReader();
358     assertEquals(7, reader.numDocs());
359     reader.close();
360
361     addDoc(modifier, ++id, value);
362     addDoc(modifier, ++id, value);
363     
364     // Delete all
365     modifier.deleteAll(); 
366
367     reader = modifier.getReader();
368     assertEquals(0, reader.numDocs());
369     reader.close();
370     
371
372     // Roll it back
373     modifier.rollback();
374     modifier.close();
375     
376     // Validate that the docs are still there
377     reader = IndexReader.open(dir, true);
378     assertEquals(7, reader.numDocs());
379     reader.close();
380     
381     dir.close();
382   }
383
384
385   private void updateDoc(IndexWriter modifier, int id, int value)
386       throws IOException {
387     Document doc = new Document();
388     doc.add(newField("content", "aaa", Field.Store.NO, Field.Index.ANALYZED));
389     doc.add(newField("id", String.valueOf(id), Field.Store.YES,
390         Field.Index.NOT_ANALYZED));
391     doc.add(newField("value", String.valueOf(value), Field.Store.NO,
392         Field.Index.NOT_ANALYZED));
393     modifier.updateDocument(new Term("id", String.valueOf(id)), doc);
394   }
395
396
397   private void addDoc(IndexWriter modifier, int id, int value)
398       throws IOException {
399     Document doc = new Document();
400     doc.add(newField("content", "aaa", Field.Store.NO, Field.Index.ANALYZED));
401     doc.add(newField("id", String.valueOf(id), Field.Store.YES,
402         Field.Index.NOT_ANALYZED));
403     doc.add(newField("value", String.valueOf(value), Field.Store.NO,
404         Field.Index.NOT_ANALYZED));
405     modifier.addDocument(doc);
406   }
407
408   private int getHitCount(Directory dir, Term term) throws IOException {
409     IndexSearcher searcher = new IndexSearcher(dir, true);
410     int hitCount = searcher.search(new TermQuery(term), null, 1000).totalHits;
411     searcher.close();
412     return hitCount;
413   }
414
415   public void testDeletesOnDiskFull() throws IOException {
416     doTestOperationsOnDiskFull(false);
417   }
418
419   public void testUpdatesOnDiskFull() throws IOException {
420     doTestOperationsOnDiskFull(true);
421   }
422
423   /**
424    * Make sure if modifier tries to commit but hits disk full that modifier
425    * remains consistent and usable. Similar to TestIndexReader.testDiskFull().
426    */
427   private void doTestOperationsOnDiskFull(boolean updates) throws IOException {
428
429     Term searchTerm = new Term("content", "aaa");
430     int START_COUNT = 157;
431     int END_COUNT = 144;
432
433     // First build up a starting index:
434     MockDirectoryWrapper startDir = newDirectory();
435     // TODO: find the resource leak that only occurs sometimes here.
436     startDir.setNoDeleteOpenFile(false);
437     IndexWriter writer = new IndexWriter(startDir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)));
438     for (int i = 0; i < 157; i++) {
439       Document d = new Document();
440       d.add(newField("id", Integer.toString(i), Field.Store.YES,
441                       Field.Index.NOT_ANALYZED));
442       d.add(newField("content", "aaa " + i, Field.Store.NO,
443                       Field.Index.ANALYZED));
444       writer.addDocument(d);
445     }
446     writer.close();
447
448     long diskUsage = startDir.sizeInBytes();
449     long diskFree = diskUsage + 10;
450
451     IOException err = null;
452
453     boolean done = false;
454
455     // Iterate w/ ever increasing free disk space:
456     while (!done) {
457       if (VERBOSE) {
458         System.out.println("TEST: cycle");
459       }
460       MockDirectoryWrapper dir = new MockDirectoryWrapper(random, new RAMDirectory(startDir));
461       dir.setPreventDoubleWrite(false);
462       IndexWriter modifier = new IndexWriter(dir,
463                                              newIndexWriterConfig(
464                                                                   TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false))
465                                              .setMaxBufferedDocs(1000)
466                                              .setMaxBufferedDeleteTerms(1000)
467                                              .setMergeScheduler(new ConcurrentMergeScheduler()));
468       ((ConcurrentMergeScheduler) modifier.getConfig().getMergeScheduler()).setSuppressExceptions();
469       modifier.setInfoStream(VERBOSE ? System.out : null);
470
471       // For each disk size, first try to commit against
472       // dir that will hit random IOExceptions & disk
473       // full; after, give it infinite disk space & turn
474       // off random IOExceptions & retry w/ same reader:
475       boolean success = false;
476
477       for (int x = 0; x < 2; x++) {
478         if (VERBOSE) {
479           System.out.println("TEST: x=" + x);
480         }
481
482         double rate = 0.1;
483         double diskRatio = ((double)diskFree) / diskUsage;
484         long thisDiskFree;
485         String testName;
486
487         if (0 == x) {
488           thisDiskFree = diskFree;
489           if (diskRatio >= 2.0) {
490             rate /= 2;
491           }
492           if (diskRatio >= 4.0) {
493             rate /= 2;
494           }
495           if (diskRatio >= 6.0) {
496             rate = 0.0;
497           }
498           if (VERBOSE) {
499             System.out.println("\ncycle: " + diskFree + " bytes");
500           }
501           testName = "disk full during reader.close() @ " + thisDiskFree
502             + " bytes";
503         } else {
504           thisDiskFree = 0;
505           rate = 0.0;
506           if (VERBOSE) {
507             System.out.println("\ncycle: same writer: unlimited disk space");
508           }
509           testName = "reader re-use after disk full";
510         }
511
512         dir.setMaxSizeInBytes(thisDiskFree);
513         dir.setRandomIOExceptionRate(rate);
514
515         try {
516           if (0 == x) {
517             int docId = 12;
518             for (int i = 0; i < 13; i++) {
519               if (updates) {
520                 Document d = new Document();
521                 d.add(newField("id", Integer.toString(i), Field.Store.YES,
522                                 Field.Index.NOT_ANALYZED));
523                 d.add(newField("content", "bbb " + i, Field.Store.NO,
524                                 Field.Index.ANALYZED));
525                 modifier.updateDocument(new Term("id", Integer.toString(docId)), d);
526               } else { // deletes
527                 modifier.deleteDocuments(new Term("id", Integer.toString(docId)));
528                 // modifier.setNorm(docId, "contents", (float)2.0);
529               }
530               docId += 12;
531             }
532           }
533           modifier.close();
534           success = true;
535           if (0 == x) {
536             done = true;
537           }
538         }
539         catch (IOException e) {
540           if (VERBOSE) {
541             System.out.println("  hit IOException: " + e);
542             e.printStackTrace(System.out);
543           }
544           err = e;
545           if (1 == x) {
546             e.printStackTrace();
547             fail(testName + " hit IOException after disk space was freed up");
548           }
549         }
550
551         if (!success) {
552           // Must force the close else the writer can have
553           // open files which cause exc in MockRAMDir.close
554           modifier.rollback();
555         }
556
557         // If the close() succeeded, make sure there are
558         // no unreferenced files.
559         if (success) {
560           _TestUtil.checkIndex(dir);
561           TestIndexWriter.assertNoUnreferencedFiles(dir, "after writer.close");
562         }
563
564         // Finally, verify index is not corrupt, and, if
565         // we succeeded, we see all docs changed, and if
566         // we failed, we see either all docs or no docs
567         // changed (transactional semantics):
568         IndexReader newReader = null;
569         try {
570           newReader = IndexReader.open(dir, true);
571         }
572         catch (IOException e) {
573           e.printStackTrace();
574           fail(testName
575                + ":exception when creating IndexReader after disk full during close: "
576                + e);
577         }
578
579         IndexSearcher searcher = newSearcher(newReader);
580         ScoreDoc[] hits = null;
581         try {
582           hits = searcher.search(new TermQuery(searchTerm), null, 1000).scoreDocs;
583         }
584         catch (IOException e) {
585           e.printStackTrace();
586           fail(testName + ": exception when searching: " + e);
587         }
588         int result2 = hits.length;
589         if (success) {
590           if (x == 0 && result2 != END_COUNT) {
591             fail(testName
592                  + ": method did not throw exception but hits.length for search on term 'aaa' is "
593                  + result2 + " instead of expected " + END_COUNT);
594           } else if (x == 1 && result2 != START_COUNT && result2 != END_COUNT) {
595             // It's possible that the first exception was
596             // "recoverable" wrt pending deletes, in which
597             // case the pending deletes are retained and
598             // then re-flushing (with plenty of disk
599             // space) will succeed in flushing the
600             // deletes:
601             fail(testName
602                  + ": method did not throw exception but hits.length for search on term 'aaa' is "
603                  + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT);
604           }
605         } else {
606           // On hitting exception we still may have added
607           // all docs:
608           if (result2 != START_COUNT && result2 != END_COUNT) {
609             err.printStackTrace();
610             fail(testName
611                  + ": method did throw exception but hits.length for search on term 'aaa' is "
612                  + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT);
613           }
614         }
615
616         searcher.close();
617         newReader.close();
618       }
619
620       modifier.close();
621       dir.close();
622
623       // Try again with 10 more bytes of free space:
624       diskFree += 10;
625     }
626     startDir.close();
627   }
628
629   // This test tests that buffered deletes are cleared when
630   // an Exception is hit during flush.
631   public void testErrorAfterApplyDeletes() throws IOException {
632     
633     MockDirectoryWrapper.Failure failure = new MockDirectoryWrapper.Failure() {
634         boolean sawMaybe = false;
635         boolean failed = false;
636         Thread thread;
637         @Override
638         public MockDirectoryWrapper.Failure reset() {
639           thread = Thread.currentThread();
640           sawMaybe = false;
641           failed = false;
642           return this;
643         }
644         @Override
645         public void eval(MockDirectoryWrapper dir)  throws IOException {
646           if (Thread.currentThread() != thread) {
647             // don't fail during merging
648             return;
649           }
650           if (sawMaybe && !failed) {
651             boolean seen = false;
652             StackTraceElement[] trace = new Exception().getStackTrace();
653             for (int i = 0; i < trace.length; i++) {
654               if ("applyDeletes".equals(trace[i].getMethodName())) {
655                 seen = true;
656                 break;
657               }
658             }
659             if (!seen) {
660               // Only fail once we are no longer in applyDeletes
661               failed = true;
662               if (VERBOSE) {
663                 System.out.println("TEST: mock failure: now fail");
664                 new Throwable().printStackTrace(System.out);
665               }
666               throw new IOException("fail after applyDeletes");
667             }
668           }
669           if (!failed) {
670             StackTraceElement[] trace = new Exception().getStackTrace();
671             for (int i = 0; i < trace.length; i++) {
672               if ("applyDeletes".equals(trace[i].getMethodName())) {
673                 if (VERBOSE) {
674                   System.out.println("TEST: mock failure: saw applyDeletes");
675                   new Throwable().printStackTrace(System.out);
676                 }
677                 sawMaybe = true;
678                 break;
679               }
680             }
681           }
682         }
683       };
684
685     // create a couple of files
686
687     String[] keywords = { "1", "2" };
688     String[] unindexed = { "Netherlands", "Italy" };
689     String[] unstored = { "Amsterdam has lots of bridges",
690         "Venice has lots of canals" };
691     String[] text = { "Amsterdam", "Venice" };
692
693     MockDirectoryWrapper dir = newDirectory();
694     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig(
695                                                                      TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)).setMaxBufferedDeleteTerms(2).setReaderPooling(false).setMergePolicy(newLogMergePolicy()));
696     modifier.setInfoStream(VERBOSE ? System.out : null);
697
698     LogMergePolicy lmp = (LogMergePolicy) modifier.getConfig().getMergePolicy();
699     lmp.setUseCompoundFile(true);
700
701     dir.failOn(failure.reset());
702
703     for (int i = 0; i < keywords.length; i++) {
704       Document doc = new Document();
705       doc.add(newField("id", keywords[i], Field.Store.YES,
706                         Field.Index.NOT_ANALYZED));
707       doc.add(newField("country", unindexed[i], Field.Store.YES,
708                         Field.Index.NO));
709       doc.add(newField("contents", unstored[i], Field.Store.NO,
710                         Field.Index.ANALYZED));
711       doc.add(newField("city", text[i], Field.Store.YES,
712                         Field.Index.ANALYZED));
713       modifier.addDocument(doc);
714     }
715     // flush (and commit if ac)
716
717     if (VERBOSE) {
718       System.out.println("TEST: now optimize");
719     }
720
721     modifier.optimize();
722     if (VERBOSE) {
723       System.out.println("TEST: now commit");
724     }
725     modifier.commit();
726
727     // one of the two files hits
728
729     Term term = new Term("city", "Amsterdam");
730     int hitCount = getHitCount(dir, term);
731     assertEquals(1, hitCount);
732
733     // open the writer again (closed above)
734
735     // delete the doc
736     // max buf del terms is two, so this is buffered
737
738     if (VERBOSE) {
739       System.out.println("TEST: delete term=" + term);
740     }
741
742     modifier.deleteDocuments(term);
743
744     // add a doc (needed for the !ac case; see below)
745     // doc remains buffered
746
747     if (VERBOSE) {
748       System.out.println("TEST: add empty doc");
749     }
750     Document doc = new Document();
751     modifier.addDocument(doc);
752
753     // commit the changes, the buffered deletes, and the new doc
754
755     // The failure object will fail on the first write after the del
756     // file gets created when processing the buffered delete
757
758     // in the ac case, this will be when writing the new segments
759     // files so we really don't need the new doc, but it's harmless
760
761     // a new segments file won't be created but in this
762     // case, creation of the cfs file happens next so we
763     // need the doc (to test that it's okay that we don't
764     // lose deletes if failing while creating the cfs file)
765     boolean failed = false;
766     try {
767       if (VERBOSE) {
768         System.out.println("TEST: now commit for failure");
769       }
770       modifier.commit();
771     } catch (IOException ioe) {
772       // expected
773       failed = true;
774     }
775
776     assertTrue(failed);
777
778     // The commit above failed, so we need to retry it (which will
779     // succeed, because the failure is a one-shot)
780
781     modifier.commit();
782
783     hitCount = getHitCount(dir, term);
784
785     // Make sure the delete was successfully flushed:
786     assertEquals(0, hitCount);
787
788     modifier.close();
789     dir.close();
790   }
791
792   // This test tests that the files created by the docs writer before
793   // a segment is written are cleaned up if there's an i/o error
794
795   public void testErrorInDocsWriterAdd() throws IOException {
796     
797     MockDirectoryWrapper.Failure failure = new MockDirectoryWrapper.Failure() {
798         boolean failed = false;
799         @Override
800         public MockDirectoryWrapper.Failure reset() {
801           failed = false;
802           return this;
803         }
804         @Override
805         public void eval(MockDirectoryWrapper dir)  throws IOException {
806           if (!failed) {
807             failed = true;
808             throw new IOException("fail in add doc");
809           }
810         }
811       };
812
813     // create a couple of files
814
815     String[] keywords = { "1", "2" };
816     String[] unindexed = { "Netherlands", "Italy" };
817     String[] unstored = { "Amsterdam has lots of bridges",
818         "Venice has lots of canals" };
819     String[] text = { "Amsterdam", "Venice" };
820
821     MockDirectoryWrapper dir = newDirectory();
822     IndexWriter modifier = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random, MockTokenizer.WHITESPACE, false)));
823     modifier.commit();
824     dir.failOn(failure.reset());
825
826     for (int i = 0; i < keywords.length; i++) {
827       Document doc = new Document();
828       doc.add(newField("id", keywords[i], Field.Store.YES,
829                         Field.Index.NOT_ANALYZED));
830       doc.add(newField("country", unindexed[i], Field.Store.YES,
831                         Field.Index.NO));
832       doc.add(newField("contents", unstored[i], Field.Store.NO,
833                         Field.Index.ANALYZED));
834       doc.add(newField("city", text[i], Field.Store.YES,
835                         Field.Index.ANALYZED));
836       try {
837         modifier.addDocument(doc);
838       } catch (IOException io) {
839         if (VERBOSE) {
840           System.out.println("TEST: got expected exc:");
841           io.printStackTrace(System.out);
842         }
843         break;
844       }
845     }
846
847     modifier.close();
848     TestIndexWriter.assertNoUnreferencedFiles(dir, "docswriter abort() failed to delete unreferenced files");    
849     dir.close();
850   }
851
852   private String arrayToString(String[] l) {
853     String s = "";
854     for (int i = 0; i < l.length; i++) {
855       if (i > 0) {
856         s += "\n    ";
857       }
858       s += l[i];
859     }
860     return s;
861   }
862   
863   public void testDeleteAllSlowly() throws Exception {
864     final Directory dir = newDirectory();
865     RandomIndexWriter w = new RandomIndexWriter(random, dir);
866     final int NUM_DOCS = atLeast(1000);
867     final List<Integer> ids = new ArrayList<Integer>(NUM_DOCS);
868     for(int id=0;id<NUM_DOCS;id++) {
869       ids.add(id);
870     }
871     Collections.shuffle(ids, random);
872     for(int id : ids) {
873       Document doc = new Document();
874       doc.add(newField("id", ""+id, Field.Index.NOT_ANALYZED));
875       w.addDocument(doc);
876     }
877     Collections.shuffle(ids, random);
878     int upto = 0;
879     while(upto < ids.size()) {
880       final int left = ids.size() - upto;
881       final int inc = Math.min(left, _TestUtil.nextInt(random, 1, 20));
882       final int limit = upto + inc;
883       while(upto < limit) {
884         w.deleteDocuments(new Term("id", ""+ids.get(upto++)));
885       }
886       final IndexReader r = w.getReader();
887       assertEquals(NUM_DOCS - upto, r.numDocs());
888       r.close();
889     }
890
891     w.close();
892     dir.close();
893   }
894   
895   public void testIndexingThenDeleting() throws Exception {
896     final Random r = random;
897     Directory dir = newDirectory();
898     // note this test explicitly disables payloads
899     final Analyzer analyzer = new Analyzer() {
900       @Override
901       public TokenStream tokenStream(String fieldName, Reader reader) {
902         return new MockTokenizer(reader, MockTokenizer.WHITESPACE, true);
903       }
904     };
905     IndexWriter w = new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, analyzer).setRAMBufferSizeMB(1.0).setMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH).setMaxBufferedDeleteTerms(IndexWriterConfig.DISABLE_AUTO_FLUSH));
906     w.setInfoStream(VERBOSE ? System.out : null);
907     Document doc = new Document();
908     doc.add(newField("field", "go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", Field.Store.NO, Field.Index.ANALYZED));
909     int num = atLeast(3);
910     for (int iter = 0; iter < num; iter++) {
911       int count = 0;
912
913       final boolean doIndexing = r.nextBoolean();
914       if (VERBOSE) {
915         System.out.println("TEST: iter doIndexing=" + doIndexing);
916       }
917       if (doIndexing) {
918         // Add docs until a flush is triggered
919         final int startFlushCount = w.getFlushCount();
920         while(w.getFlushCount() == startFlushCount) {
921           w.addDocument(doc);
922           count++;
923         }
924       } else {
925         // Delete docs until a flush is triggered
926         final int startFlushCount = w.getFlushCount();
927         while(w.getFlushCount() == startFlushCount) {
928           w.deleteDocuments(new Term("foo", ""+count));
929           count++;
930         }
931       }
932       assertTrue("flush happened too quickly during " + (doIndexing ? "indexing" : "deleting") + " count=" + count, count > 3000);
933     }
934     w.close();
935     dir.close();
936   }
937 }