Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id ECADC200C37 for ; Mon, 27 Feb 2017 00:36:55 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id EB322160B91; Sun, 26 Feb 2017 23:36:55 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 846F3160B78 for ; Mon, 27 Feb 2017 00:36:53 +0100 (CET) Received: (qmail 39504 invoked by uid 500); 26 Feb 2017 23:36:51 -0000 Mailing-List: contact commits-help@lucenenet.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: lucene-net-dev@lucenenet.apache.org Delivered-To: mailing list commits@lucenenet.apache.org Received: (qmail 37982 invoked by uid 99); 26 Feb 2017 23:36:50 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 26 Feb 2017 23:36:50 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 30E5CE008F; Sun, 26 Feb 2017 23:36:50 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: nightowl888@apache.org To: commits@lucenenet.apache.org Date: Sun, 26 Feb 2017 23:37:18 -0000 Message-Id: <36f008d6d462414dab36e7b765771691@git.apache.org> In-Reply-To: <1e7e127cca214d5a80ee73510374469b@git.apache.org> References: <1e7e127cca214d5a80ee73510374469b@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [30/72] [abbrv] [partial] lucenenet git commit: Lucene.Net.Tests: Removed \core directory and put its contents in root directory archived-at: Sun, 26 Feb 2017 23:36:56 -0000 http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Index/TestIndexWriterDelete.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Index/TestIndexWriterDelete.cs b/src/Lucene.Net.Tests/Index/TestIndexWriterDelete.cs new file mode 100644 index 0000000..6ac040a --- /dev/null +++ b/src/Lucene.Net.Tests/Index/TestIndexWriterDelete.cs @@ -0,0 +1,1447 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using Lucene.Net.Attributes; +using Lucene.Net.Documents; + +namespace Lucene.Net.Index +{ + using Lucene.Net.Randomized.Generators; + using Lucene.Net.Support; + using NUnit.Framework; + using System.IO; + using Util; + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + using Analyzer = Lucene.Net.Analysis.Analyzer; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + using Field = Field; + using FieldType = FieldType; + using IndexSearcher = Lucene.Net.Search.IndexSearcher; + using IOUtils = Lucene.Net.Util.IOUtils; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; + using MockDirectoryWrapper = Lucene.Net.Store.MockDirectoryWrapper; + using MockTokenizer = Lucene.Net.Analysis.MockTokenizer; + using NumericDocValuesField = NumericDocValuesField; + using RAMDirectory = Lucene.Net.Store.RAMDirectory; + using ScoreDoc = Lucene.Net.Search.ScoreDoc; + using StringField = StringField; + using TermQuery = Lucene.Net.Search.TermQuery; + using TestUtil = Lucene.Net.Util.TestUtil; + + [TestFixture] + public class TestIndexWriterDelete : LuceneTestCase + { + // test the simple case + [Test] + public virtual void TestSimpleCase() + { + string[] keywords = new string[] { "1", "2" }; + string[] unindexed = new string[] { "Netherlands", "Italy" }; + string[] unstored = new string[] { "Amsterdam has lots of bridges", "Venice has lots of canals" }; + string[] text = new string[] { "Amsterdam", "Venice" }; + + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDeleteTerms(1)); + + FieldType custom1 = new FieldType(); + custom1.IsStored = true; + for (int i = 0; i < keywords.Length; i++) + { + Document doc = new Document(); + doc.Add(NewStringField("id", keywords[i], Field.Store.YES)); + doc.Add(NewField("country", unindexed[i], custom1)); + doc.Add(NewTextField("contents", unstored[i], Field.Store.NO)); + doc.Add(NewTextField("city", text[i], Field.Store.YES)); + modifier.AddDocument(doc); + } + modifier.ForceMerge(1); + modifier.Commit(); + + Term term = new Term("city", "Amsterdam"); + int hitCount = GetHitCount(dir, term); + Assert.AreEqual(1, hitCount); + if (VERBOSE) + { + Console.WriteLine("\nTEST: now delete by term=" + term); + } + modifier.DeleteDocuments(term); + modifier.Commit(); + + if (VERBOSE) + { + Console.WriteLine("\nTEST: now getHitCount"); + } + hitCount = GetHitCount(dir, term); + Assert.AreEqual(0, hitCount); + + modifier.Dispose(); + dir.Dispose(); + } + + // test when delete terms only apply to disk segments + [Test] + public virtual void TestNonRAMDelete() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2)); + int id = 0; + int value = 100; + + for (int i = 0; i < 7; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.Commit(); + + Assert.AreEqual(0, modifier.NumBufferedDocuments); + Assert.IsTrue(0 < modifier.SegmentCount); + + modifier.Commit(); + + IndexReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + modifier.DeleteDocuments(new Term("value", Convert.ToString(value))); + + modifier.Commit(); + + reader = DirectoryReader.Open(dir); + Assert.AreEqual(0, reader.NumDocs); + reader.Dispose(); + modifier.Dispose(); + dir.Dispose(); + } + + [Test] + public virtual void TestMaxBufferedDeletes() + { + Directory dir = NewDirectory(); + IndexWriter writer = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDeleteTerms(1)); + + writer.AddDocument(new Document()); + writer.DeleteDocuments(new Term("foobar", "1")); + writer.DeleteDocuments(new Term("foobar", "1")); + writer.DeleteDocuments(new Term("foobar", "1")); + Assert.AreEqual(3, writer.FlushDeletesCount); + writer.Dispose(); + dir.Dispose(); + } + + // test when delete terms only apply to ram segments + [Test] + public virtual void TestRAMDeletes() + { + for (int t = 0; t < 2; t++) + { + if (VERBOSE) + { + Console.WriteLine("TEST: t=" + t); + } + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(4).SetMaxBufferedDeleteTerms(4)); + int id = 0; + int value = 100; + + AddDoc(modifier, ++id, value); + if (0 == t) + { + modifier.DeleteDocuments(new Term("value", Convert.ToString(value))); + } + else + { + modifier.DeleteDocuments(new TermQuery(new Term("value", Convert.ToString(value)))); + } + AddDoc(modifier, ++id, value); + if (0 == t) + { + modifier.DeleteDocuments(new Term("value", Convert.ToString(value))); + Assert.AreEqual(2, modifier.NumBufferedDeleteTerms); + Assert.AreEqual(1, modifier.BufferedDeleteTermsSize); + } + else + { + modifier.DeleteDocuments(new TermQuery(new Term("value", Convert.ToString(value)))); + } + + AddDoc(modifier, ++id, value); + Assert.AreEqual(0, modifier.SegmentCount); + modifier.Commit(); + + IndexReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(1, reader.NumDocs); + + int hitCount = GetHitCount(dir, new Term("id", Convert.ToString(id))); + Assert.AreEqual(1, hitCount); + reader.Dispose(); + modifier.Dispose(); + dir.Dispose(); + } + } + + // test when delete terms apply to both disk and ram segments + [Test] + public virtual void TestBothDeletes() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(100).SetMaxBufferedDeleteTerms(100)); + + int id = 0; + int value = 100; + + for (int i = 0; i < 5; i++) + { + AddDoc(modifier, ++id, value); + } + + value = 200; + for (int i = 0; i < 5; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.Commit(); + + for (int i = 0; i < 5; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.DeleteDocuments(new Term("value", Convert.ToString(value))); + + modifier.Commit(); + + IndexReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(5, reader.NumDocs); + modifier.Dispose(); + reader.Dispose(); + dir.Dispose(); + } + + // test that batched delete terms are flushed together + [Test] + public virtual void TestBatchDeletes() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2)); + + int id = 0; + int value = 100; + + for (int i = 0; i < 7; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.Commit(); + + IndexReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + id = 0; + modifier.DeleteDocuments(new Term("id", Convert.ToString(++id))); + modifier.DeleteDocuments(new Term("id", Convert.ToString(++id))); + + modifier.Commit(); + + reader = DirectoryReader.Open(dir); + Assert.AreEqual(5, reader.NumDocs); + reader.Dispose(); + + Term[] terms = new Term[3]; + for (int i = 0; i < terms.Length; i++) + { + terms[i] = new Term("id", Convert.ToString(++id)); + } + modifier.DeleteDocuments(terms); + modifier.Commit(); + reader = DirectoryReader.Open(dir); + Assert.AreEqual(2, reader.NumDocs); + reader.Dispose(); + + modifier.Dispose(); + dir.Dispose(); + } + + // test deleteAll() + [Test] + public virtual void TestDeleteAll() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2)); + + int id = 0; + int value = 100; + + for (int i = 0; i < 7; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.Commit(); + + IndexReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + // Add 1 doc (so we will have something buffered) + AddDoc(modifier, 99, value); + + // Delete all + modifier.DeleteAll(); + + // Delete all shouldn't be on disk yet + reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + // Add a doc and update a doc (after the deleteAll, before the commit) + AddDoc(modifier, 101, value); + UpdateDoc(modifier, 102, value); + + // commit the delete all + modifier.Commit(); + + // Validate there are no docs left + reader = DirectoryReader.Open(dir); + Assert.AreEqual(2, reader.NumDocs); + reader.Dispose(); + + modifier.Dispose(); + dir.Dispose(); + } + + [Test] + public virtual void TestDeleteAllNoDeadLock() + { + Directory dir = NewDirectory(); + RandomIndexWriter modifier = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + int numThreads = AtLeast(2); + ThreadClass[] threads = new ThreadClass[numThreads]; + CountdownEvent latch = new CountdownEvent(1); + CountdownEvent doneLatch = new CountdownEvent(numThreads); + for (int i = 0; i < numThreads; i++) + { + int offset = i; + threads[i] = new ThreadAnonymousInnerClassHelper(this, modifier, latch, doneLatch, offset); + threads[i].Start(); + } + latch.Signal(); + //Wait for 1 millisecond + while (!doneLatch.Wait(new TimeSpan(0, 0, 0, 0, 1))) + { + modifier.DeleteAll(); + if (VERBOSE) + { + Console.WriteLine("del all"); + } + } + + modifier.DeleteAll(); + foreach (ThreadClass thread in threads) + { + thread.Join(); + } + + modifier.Dispose(); + DirectoryReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(reader.MaxDoc, 0); + Assert.AreEqual(reader.NumDocs, 0); + Assert.AreEqual(reader.NumDeletedDocs, 0); + reader.Dispose(); + + dir.Dispose(); + } + + private class ThreadAnonymousInnerClassHelper : ThreadClass + { + private readonly TestIndexWriterDelete OuterInstance; + + private RandomIndexWriter Modifier; + private CountdownEvent Latch; + private CountdownEvent DoneLatch; + private int Offset; + + public ThreadAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance, RandomIndexWriter modifier, CountdownEvent latch, CountdownEvent doneLatch, int offset) + { + this.OuterInstance = outerInstance; + this.Modifier = modifier; + this.Latch = latch; + this.DoneLatch = doneLatch; + this.Offset = offset; + } + + public override void Run() + { + int id = Offset * 1000; + int value = 100; + try + { + Latch.Wait(); + for (int j = 0; j < 1000; j++) + { + Document doc = new Document(); + doc.Add(OuterInstance.NewTextField("content", "aaa", Field.Store.NO)); + doc.Add(OuterInstance.NewStringField("id", Convert.ToString(id++), Field.Store.YES)); + doc.Add(OuterInstance.NewStringField("value", Convert.ToString(value), Field.Store.NO)); + if (DefaultCodecSupportsDocValues()) + { + doc.Add(new NumericDocValuesField("dv", value)); + } + Modifier.AddDocument(doc); + if (VERBOSE) + { + Console.WriteLine("\tThread[" + Offset + "]: add doc: " + id); + } + } + } + catch (Exception e) + { + throw new Exception(e.Message, e); + } + finally + { + DoneLatch.Signal(); + if (VERBOSE) + { + Console.WriteLine("\tThread[" + Offset + "]: done indexing"); + } + } + } + } + + // test rollback of deleteAll() + [Test] + public virtual void TestDeleteAllRollback() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2)); + + int id = 0; + int value = 100; + + for (int i = 0; i < 7; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.Commit(); + + AddDoc(modifier, ++id, value); + + IndexReader reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + // Delete all + modifier.DeleteAll(); + + // Roll it back + modifier.Rollback(); + modifier.Dispose(); + + // Validate that the docs are still there + reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + dir.Dispose(); + } + + // test deleteAll() w/ near real-time reader + [Test] + public virtual void TestDeleteAllNRT() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDocs(2).SetMaxBufferedDeleteTerms(2)); + + int id = 0; + int value = 100; + + for (int i = 0; i < 7; i++) + { + AddDoc(modifier, ++id, value); + } + modifier.Commit(); + + IndexReader reader = modifier.Reader; + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + AddDoc(modifier, ++id, value); + AddDoc(modifier, ++id, value); + + // Delete all + modifier.DeleteAll(); + + reader = modifier.Reader; + Assert.AreEqual(0, reader.NumDocs); + reader.Dispose(); + + // Roll it back + modifier.Rollback(); + modifier.Dispose(); + + // Validate that the docs are still there + reader = DirectoryReader.Open(dir); + Assert.AreEqual(7, reader.NumDocs); + reader.Dispose(); + + dir.Dispose(); + } + + private void UpdateDoc(IndexWriter modifier, int id, int value) + { + Document doc = new Document(); + doc.Add(NewTextField("content", "aaa", Field.Store.NO)); + doc.Add(NewStringField("id", Convert.ToString(id), Field.Store.YES)); + doc.Add(NewStringField("value", Convert.ToString(value), Field.Store.NO)); + if (DefaultCodecSupportsDocValues()) + { + doc.Add(new NumericDocValuesField("dv", value)); + } + modifier.UpdateDocument(new Term("id", Convert.ToString(id)), doc); + } + + private void AddDoc(IndexWriter modifier, int id, int value) + { + Document doc = new Document(); + doc.Add(NewTextField("content", "aaa", Field.Store.NO)); + doc.Add(NewStringField("id", Convert.ToString(id), Field.Store.YES)); + doc.Add(NewStringField("value", Convert.ToString(value), Field.Store.NO)); + if (DefaultCodecSupportsDocValues()) + { + doc.Add(new NumericDocValuesField("dv", value)); + } + modifier.AddDocument(doc); + } + + private int GetHitCount(Directory dir, Term term) + { + IndexReader reader = DirectoryReader.Open(dir); + IndexSearcher searcher = NewSearcher(reader); + int hitCount = searcher.Search(new TermQuery(term), null, 1000).TotalHits; + reader.Dispose(); + return hitCount; + } + + [Test] + public virtual void TestDeletesOnDiskFull( + [ValueSource(typeof(ConcurrentMergeSchedulers), "Values")]IConcurrentMergeScheduler scheduler) + { + DoTestOperationsOnDiskFull(scheduler, false); + } + + [Test] + public virtual void TestUpdatesOnDiskFull( + [ValueSource(typeof(ConcurrentMergeSchedulers), "Values")]IConcurrentMergeScheduler scheduler) + { + DoTestOperationsOnDiskFull(scheduler, true); + } + + /// + /// Make sure if modifier tries to commit but hits disk full that modifier + /// remains consistent and usable. Similar to TestIndexReader.testDiskFull(). + /// + private void DoTestOperationsOnDiskFull(IConcurrentMergeScheduler scheduler, bool updates) + { + Term searchTerm = new Term("content", "aaa"); + int START_COUNT = 157; + int END_COUNT = 144; + + // First build up a starting index: + MockDirectoryWrapper startDir = NewMockDirectory(); + // TODO: find the resource leak that only occurs sometimes here. + startDir.NoDeleteOpenFile = false; + IndexWriter writer = new IndexWriter(startDir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false))); + for (int i = 0; i < 157; i++) + { + Document d = new Document(); + d.Add(NewStringField("id", Convert.ToString(i), Field.Store.YES)); + d.Add(NewTextField("content", "aaa " + i, Field.Store.NO)); + if (DefaultCodecSupportsDocValues()) + { + d.Add(new NumericDocValuesField("dv", i)); + } + writer.AddDocument(d); + } + writer.Dispose(); + + long diskUsage = startDir.SizeInBytes(); + long diskFree = diskUsage + 10; + + IOException err = null; + + bool done = false; + + // Iterate w/ ever increasing free disk space: + while (!done) + { + if (VERBOSE) + { + Console.WriteLine("TEST: cycle"); + } + MockDirectoryWrapper dir = new MockDirectoryWrapper(Random(), new RAMDirectory(startDir, NewIOContext(Random()))); + dir.PreventDoubleWrite = false; + dir.AllowRandomFileNotFoundException = false; + + var config = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)) + .SetMaxBufferedDocs(1000) + .SetMaxBufferedDeleteTerms(1000) + .SetMergeScheduler(scheduler); + + scheduler.SetSuppressExceptions(); + + IndexWriter modifier = new IndexWriter(dir, config); + + // For each disk size, first try to commit against + // dir that will hit random IOExceptions & disk + // full; after, give it infinite disk space & turn + // off random IOExceptions & retry w/ same reader: + bool success = false; + + for (int x = 0; x < 2; x++) + { + if (VERBOSE) + { + Console.WriteLine("TEST: x=" + x); + } + + double rate = 0.1; + double diskRatio = ((double)diskFree) / diskUsage; + long thisDiskFree; + string testName; + + if (0 == x) + { + thisDiskFree = diskFree; + if (diskRatio >= 2.0) + { + rate /= 2; + } + if (diskRatio >= 4.0) + { + rate /= 2; + } + if (diskRatio >= 6.0) + { + rate = 0.0; + } + if (VERBOSE) + { + Console.WriteLine("\ncycle: " + diskFree + " bytes"); + } + testName = "disk full during reader.Dispose() @ " + thisDiskFree + " bytes"; + dir.RandomIOExceptionRateOnOpen = Random().NextDouble() * 0.01; + } + else + { + thisDiskFree = 0; + rate = 0.0; + if (VERBOSE) + { + Console.WriteLine("\ncycle: same writer: unlimited disk space"); + } + testName = "reader re-use after disk full"; + dir.RandomIOExceptionRateOnOpen = 0.0; + } + + dir.MaxSizeInBytes = thisDiskFree; + dir.RandomIOExceptionRate = rate; + + try + { + if (0 == x) + { + int docId = 12; + for (int i = 0; i < 13; i++) + { + if (updates) + { + Document d = new Document(); + d.Add(NewStringField("id", Convert.ToString(i), Field.Store.YES)); + d.Add(NewTextField("content", "bbb " + i, Field.Store.NO)); + if (DefaultCodecSupportsDocValues()) + { + d.Add(new NumericDocValuesField("dv", i)); + } + modifier.UpdateDocument(new Term("id", Convert.ToString(docId)), d); + } // deletes + else + { + modifier.DeleteDocuments(new Term("id", Convert.ToString(docId))); + // modifier.setNorm(docId, "contents", (float)2.0); + } + docId += 12; + } + } + modifier.Dispose(); + success = true; + if (0 == x) + { + done = true; + } + } + catch (IOException e) + { + if (VERBOSE) + { + Console.WriteLine(" hit IOException: " + e); + Console.WriteLine(e.StackTrace); + } + err = e; + if (1 == x) + { + Console.WriteLine(e.ToString()); + Console.Write(e.StackTrace); + Assert.Fail(testName + " hit IOException after disk space was freed up"); + } + } + // prevent throwing a random exception here!! + double randomIOExceptionRate = dir.RandomIOExceptionRate; + long maxSizeInBytes = dir.MaxSizeInBytes; + dir.RandomIOExceptionRate = 0.0; + dir.RandomIOExceptionRateOnOpen = 0.0; + dir.MaxSizeInBytes = 0; + if (!success) + { + // Must force the close else the writer can have + // open files which cause exc in MockRAMDir.close + if (VERBOSE) + { + Console.WriteLine("TEST: now rollback"); + } + modifier.Rollback(); + } + + // If the close() succeeded, make sure there are + // no unreferenced files. + if (success) + { + TestUtil.CheckIndex(dir); + TestIndexWriter.AssertNoUnreferencedFiles(dir, "after writer.close"); + } + dir.RandomIOExceptionRate = randomIOExceptionRate; + dir.MaxSizeInBytes = maxSizeInBytes; + + // Finally, verify index is not corrupt, and, if + // we succeeded, we see all docs changed, and if + // we failed, we see either all docs or no docs + // changed (transactional semantics): + IndexReader newReader = null; + try + { + newReader = DirectoryReader.Open(dir); + } + catch (IOException e) + { + Console.WriteLine(e.ToString()); + Console.Write(e.StackTrace); + Assert.Fail(testName + ":exception when creating IndexReader after disk full during close: " + e); + } + + IndexSearcher searcher = NewSearcher(newReader); + ScoreDoc[] hits = null; + try + { + hits = searcher.Search(new TermQuery(searchTerm), null, 1000).ScoreDocs; + } + catch (IOException e) + { + Console.WriteLine(e.ToString()); + Console.Write(e.StackTrace); + Assert.Fail(testName + ": exception when searching: " + e); + } + int result2 = hits.Length; + if (success) + { + if (x == 0 && result2 != END_COUNT) + { + Assert.Fail(testName + ": method did not throw exception but hits.Length for search on term 'aaa' is " + result2 + " instead of expected " + END_COUNT); + } + else if (x == 1 && result2 != START_COUNT && result2 != END_COUNT) + { + // It's possible that the first exception was + // "recoverable" wrt pending deletes, in which + // case the pending deletes are retained and + // then re-flushing (with plenty of disk + // space) will succeed in flushing the + // deletes: + Assert.Fail(testName + ": method did not throw exception but hits.Length for search on term 'aaa' is " + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT); + } + } + else + { + // On hitting exception we still may have added + // all docs: + if (result2 != START_COUNT && result2 != END_COUNT) + { + Console.WriteLine(err.ToString()); + Console.Write(err.StackTrace); + Assert.Fail(testName + ": method did throw exception but hits.Length for search on term 'aaa' is " + result2 + " instead of expected " + START_COUNT + " or " + END_COUNT); + } + } + newReader.Dispose(); + if (result2 == END_COUNT) + { + break; + } + } + dir.Dispose(); + modifier.Dispose(); + + // Try again with 10 more bytes of free space: + diskFree += 10; + } + startDir.Dispose(); + } + + // this test tests that buffered deletes are cleared when + // an Exception is hit during flush. + [Test] + public virtual void TestErrorAfterApplyDeletes() + { + MockDirectoryWrapper.Failure failure = new FailureAnonymousInnerClassHelper(this); + + // create a couple of files + + string[] keywords = new string[] { "1", "2" }; + string[] unindexed = new string[] { "Netherlands", "Italy" }; + string[] unstored = new string[] { "Amsterdam has lots of bridges", "Venice has lots of canals" }; + string[] text = new string[] { "Amsterdam", "Venice" }; + + MockDirectoryWrapper dir = NewMockDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false)).SetMaxBufferedDeleteTerms(2).SetReaderPooling(false).SetMergePolicy(NewLogMergePolicy())); + + MergePolicy lmp = modifier.Config.MergePolicy; + lmp.NoCFSRatio = 1.0; + + dir.FailOn(failure.Reset()); + + FieldType custom1 = new FieldType(); + custom1.IsStored = true; + for (int i = 0; i < keywords.Length; i++) + { + Document doc = new Document(); + doc.Add(NewStringField("id", keywords[i], Field.Store.YES)); + doc.Add(NewField("country", unindexed[i], custom1)); + doc.Add(NewTextField("contents", unstored[i], Field.Store.NO)); + doc.Add(NewTextField("city", text[i], Field.Store.YES)); + modifier.AddDocument(doc); + } + // flush (and commit if ac) + + if (VERBOSE) + { + Console.WriteLine("TEST: now full merge"); + } + + modifier.ForceMerge(1); + if (VERBOSE) + { + Console.WriteLine("TEST: now commit"); + } + modifier.Commit(); + + // one of the two files hits + + Term term = new Term("city", "Amsterdam"); + int hitCount = GetHitCount(dir, term); + Assert.AreEqual(1, hitCount); + + // open the writer again (closed above) + + // delete the doc + // max buf del terms is two, so this is buffered + + if (VERBOSE) + { + Console.WriteLine("TEST: delete term=" + term); + } + + modifier.DeleteDocuments(term); + + // add a doc (needed for the !ac case; see below) + // doc remains buffered + + if (VERBOSE) + { + Console.WriteLine("TEST: add empty doc"); + } + Document doc_ = new Document(); + modifier.AddDocument(doc_); + + // commit the changes, the buffered deletes, and the new doc + + // The failure object will fail on the first write after the del + // file gets created when processing the buffered delete + + // in the ac case, this will be when writing the new segments + // files so we really don't need the new doc, but it's harmless + + // a new segments file won't be created but in this + // case, creation of the cfs file happens next so we + // need the doc (to test that it's okay that we don't + // lose deletes if failing while creating the cfs file) + bool failed = false; + try + { + if (VERBOSE) + { + Console.WriteLine("TEST: now commit for failure"); + } + modifier.Commit(); + } +#pragma warning disable 168 + catch (IOException ioe) +#pragma warning restore 168 + { + // expected + failed = true; + } + + Assert.IsTrue(failed); + + // The commit above failed, so we need to retry it (which will + // succeed, because the failure is a one-shot) + + modifier.Commit(); + + hitCount = GetHitCount(dir, term); + + // Make sure the delete was successfully flushed: + Assert.AreEqual(0, hitCount); + + modifier.Dispose(); + dir.Dispose(); + } + + private class FailureAnonymousInnerClassHelper : MockDirectoryWrapper.Failure + { + private readonly TestIndexWriterDelete OuterInstance; + + public FailureAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance) + { + this.OuterInstance = outerInstance; + sawMaybe = false; + failed = false; + } + + internal bool sawMaybe; + internal bool failed; + internal Thread thread; + + public override MockDirectoryWrapper.Failure Reset() + { + thread = Thread.CurrentThread; + sawMaybe = false; + failed = false; + return this; + } + + public override void Eval(MockDirectoryWrapper dir) + { + if (Thread.CurrentThread != thread) + { + // don't fail during merging + return; + } + if (sawMaybe && !failed) + { + bool seen = + StackTraceHelper.DoesStackTraceContainMethod("ApplyDeletesAndUpdates") || + StackTraceHelper.DoesStackTraceContainMethod("SlowFileExists"); + + if (!seen) + { + // Only fail once we are no longer in applyDeletes + failed = true; + if (VERBOSE) + { + Console.WriteLine("TEST: mock failure: now fail"); + Console.WriteLine(Environment.StackTrace); + } + throw new IOException("fail after applyDeletes"); + } + } + if (!failed) + { + if (StackTraceHelper.DoesStackTraceContainMethod("ApplyDeletesAndUpdates")) + { + if (VERBOSE) + { + Console.WriteLine("TEST: mock failure: saw applyDeletes"); + Console.WriteLine(Environment.StackTrace); + } + sawMaybe = true; + } + } + } + } + + // this test tests that the files created by the docs writer before + // a segment is written are cleaned up if there's an i/o error + [Test] + public virtual void TestErrorInDocsWriterAdd() + { + MockDirectoryWrapper.Failure failure = new FailureAnonymousInnerClassHelper2(this); + + // create a couple of files + + string[] keywords = new string[] { "1", "2" }; + string[] unindexed = new string[] { "Netherlands", "Italy" }; + string[] unstored = new string[] { "Amsterdam has lots of bridges", "Venice has lots of canals" }; + string[] text = new string[] { "Amsterdam", "Venice" }; + + MockDirectoryWrapper dir = NewMockDirectory(); + IndexWriter modifier = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false))); + modifier.Commit(); + dir.FailOn(failure.Reset()); + + FieldType custom1 = new FieldType(); + custom1.IsStored = true; + for (int i = 0; i < keywords.Length; i++) + { + Document doc = new Document(); + doc.Add(NewStringField("id", keywords[i], Field.Store.YES)); + doc.Add(NewField("country", unindexed[i], custom1)); + doc.Add(NewTextField("contents", unstored[i], Field.Store.NO)); + doc.Add(NewTextField("city", text[i], Field.Store.YES)); + try + { + modifier.AddDocument(doc); + } + catch (IOException io) + { + if (VERBOSE) + { + Console.WriteLine("TEST: got expected exc:"); + Console.WriteLine(io.StackTrace); + } + break; + } + } + + modifier.Dispose(); + TestIndexWriter.AssertNoUnreferencedFiles(dir, "docsWriter.abort() failed to delete unreferenced files"); + dir.Dispose(); + } + + private class FailureAnonymousInnerClassHelper2 : MockDirectoryWrapper.Failure + { + private readonly TestIndexWriterDelete OuterInstance; + + public FailureAnonymousInnerClassHelper2(TestIndexWriterDelete outerInstance) + { + this.OuterInstance = outerInstance; + failed = false; + } + + internal bool failed; + + public override MockDirectoryWrapper.Failure Reset() + { + failed = false; + return this; + } + + public override void Eval(MockDirectoryWrapper dir) + { + if (!failed) + { + failed = true; + throw new IOException("fail in add doc"); + } + } + } + + [Test] + public virtual void TestDeleteNullQuery() + { + Directory dir = NewDirectory(); + IndexWriter modifier = new IndexWriter(dir, new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.WHITESPACE, false))); + + for (int i = 0; i < 5; i++) + { + AddDoc(modifier, i, 2 * i); + } + + modifier.DeleteDocuments(new TermQuery(new Term("nada", "nada"))); + modifier.Commit(); + Assert.AreEqual(5, modifier.NumDocs); + modifier.Dispose(); + dir.Dispose(); + } + + [Test] + public virtual void TestDeleteAllSlowly() + { + Directory dir = NewDirectory(); + RandomIndexWriter w = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + int NUM_DOCS = AtLeast(1000); + IList ids = new List(NUM_DOCS); + for (int id = 0; id < NUM_DOCS; id++) + { + ids.Add(id); + } + Collections.Shuffle(ids); + foreach (int id in ids) + { + Document doc = new Document(); + doc.Add(NewStringField("id", "" + id, Field.Store.NO)); + w.AddDocument(doc); + } + Collections.Shuffle(ids); + int upto = 0; + while (upto < ids.Count) + { + int left = ids.Count - upto; + int inc = Math.Min(left, TestUtil.NextInt(Random(), 1, 20)); + int limit = upto + inc; + while (upto < limit) + { + w.DeleteDocuments(new Term("id", "" + ids[upto++])); + } + IndexReader r = w.Reader; + Assert.AreEqual(NUM_DOCS - upto, r.NumDocs); + r.Dispose(); + } + + w.Dispose(); + dir.Dispose(); + } + + [Test] + public virtual void TestIndexingThenDeleting() + { + // TODO: move this test to its own class and just @SuppressCodecs? + // TODO: is it enough to just use newFSDirectory? + string fieldFormat = TestUtil.GetPostingsFormat("field"); + AssumeFalse("this test cannot run with Memory codec", fieldFormat.Equals("Memory")); + AssumeFalse("this test cannot run with SimpleText codec", fieldFormat.Equals("SimpleText")); + AssumeFalse("this test cannot run with Direct codec", fieldFormat.Equals("Direct")); + Random r = Random(); + Directory dir = NewDirectory(); + // note this test explicitly disables payloads + Analyzer analyzer = new AnalyzerAnonymousInnerClassHelper(this); + IndexWriter w = new IndexWriter(dir, (IndexWriterConfig)NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer).SetRAMBufferSizeMB(1.0).SetMaxBufferedDocs(IndexWriterConfig.DISABLE_AUTO_FLUSH).SetMaxBufferedDeleteTerms(IndexWriterConfig.DISABLE_AUTO_FLUSH)); + Document doc = new Document(); + doc.Add(NewTextField("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)); + int num = AtLeast(3); + for (int iter = 0; iter < num; iter++) + { + int count = 0; + + bool doIndexing = r.NextBoolean(); + if (VERBOSE) + { + Console.WriteLine("TEST: iter doIndexing=" + doIndexing); + } + if (doIndexing) + { + // Add docs until a flush is triggered + int startFlushCount = w.FlushCount; + while (w.FlushCount == startFlushCount) + { + w.AddDocument(doc); + count++; + } + } + else + { + // Delete docs until a flush is triggered + int startFlushCount = w.FlushCount; + while (w.FlushCount == startFlushCount) + { + w.DeleteDocuments(new Term("foo", "" + count)); + count++; + } + } + Assert.IsTrue(count > 2500, "flush happened too quickly during " + (doIndexing ? "indexing" : "deleting") + " count=" + count); + } + w.Dispose(); + dir.Dispose(); + } + + private class AnalyzerAnonymousInnerClassHelper : Analyzer + { + private readonly TestIndexWriterDelete OuterInstance; + + public AnalyzerAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance) + { + this.OuterInstance = outerInstance; + } + + protected internal override TokenStreamComponents CreateComponents(string fieldName, TextReader reader) + { + return new TokenStreamComponents(new MockTokenizer(reader, MockTokenizer.WHITESPACE, true)); + } + } + + // LUCENE-3340: make sure deletes that we don't apply + // during flush (ie are just pushed into the stream) are + // in fact later flushed due to their RAM usage: + [Test] + public virtual void TestFlushPushedDeletesByRAM() + { + Directory dir = NewDirectory(); + // Cannot use RandomIndexWriter because we don't want to + // ever call commit() for this test: + // note: tiny rambuffer used, as with a 1MB buffer the test is too slow (flush @ 128,999) + IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetRAMBufferSizeMB(0.1f).SetMaxBufferedDocs(1000).SetMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).SetReaderPooling(false)); + int count = 0; + while (true) + { + Document doc = new Document(); + doc.Add(new StringField("id", count + "", Field.Store.NO)); + Term delTerm; + if (count == 1010) + { + // this is the only delete that applies + delTerm = new Term("id", "" + 0); + } + else + { + // These get buffered, taking up RAM, but delete + // nothing when applied: + delTerm = new Term("id", "x" + count); + } + w.UpdateDocument(delTerm, doc); + // Eventually segment 0 should get a del docs: + // TODO: fix this test + if (SlowFileExists(dir, "_0_1.del") || SlowFileExists(dir, "_0_1.liv")) + { + if (VERBOSE) + { + Console.WriteLine("TEST: deletes created @ count=" + count); + } + break; + } + count++; + + // Today we applyDeletes @ count=21553; even if we make + // sizable improvements to RAM efficiency of buffered + // del term we're unlikely to go over 100K: + if (count > 100000) + { + Assert.Fail("delete's were not applied"); + } + } + w.Dispose(); + dir.Dispose(); + } + + // LUCENE-3340: make sure deletes that we don't apply + // during flush (ie are just pushed into the stream) are + // in fact later flushed due to their RAM usage: + [Test] + public virtual void TestFlushPushedDeletesByCount() + { + Directory dir = NewDirectory(); + // Cannot use RandomIndexWriter because we don't want to + // ever call commit() for this test: + int flushAtDelCount = AtLeast(1020); + IndexWriter w = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetMaxBufferedDeleteTerms(flushAtDelCount).SetMaxBufferedDocs(1000).SetRAMBufferSizeMB(IndexWriterConfig.DISABLE_AUTO_FLUSH).SetMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).SetReaderPooling(false)); + int count = 0; + while (true) + { + Document doc = new Document(); + doc.Add(new StringField("id", count + "", Field.Store.NO)); + Term delTerm; + if (count == 1010) + { + // this is the only delete that applies + delTerm = new Term("id", "" + 0); + } + else + { + // These get buffered, taking up RAM, but delete + // nothing when applied: + delTerm = new Term("id", "x" + count); + } + w.UpdateDocument(delTerm, doc); + // Eventually segment 0 should get a del docs: + // TODO: fix this test + if (SlowFileExists(dir, "_0_1.del") || SlowFileExists(dir, "_0_1.liv")) + { + break; + } + count++; + if (count > flushAtDelCount) + { + Assert.Fail("delete's were not applied at count=" + flushAtDelCount); + } + } + w.Dispose(); + dir.Dispose(); + } + + // Make sure buffered (pushed) deletes don't use up so + // much RAM that it forces long tail of tiny segments: +#if !NETSTANDARD + // LUCENENET: There is no Timeout on NUnit for .NET Core. + [Timeout(int.MaxValue)] +#endif + [Test, LongRunningTest, HasTimeout] + public virtual void TestApplyDeletesOnFlush() + { + Directory dir = NewDirectory(); + // Cannot use RandomIndexWriter because we don't want to + // ever call commit() for this test: + AtomicInt32 docsInSegment = new AtomicInt32(); + AtomicBoolean closing = new AtomicBoolean(); + AtomicBoolean sawAfterFlush = new AtomicBoolean(); + IndexWriter w = new IndexWriterAnonymousInnerClassHelper(this, dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetRAMBufferSizeMB(0.5).SetMaxBufferedDocs(-1).SetMergePolicy(NoMergePolicy.NO_COMPOUND_FILES).SetReaderPooling(false), docsInSegment, closing, sawAfterFlush); + int id = 0; + while (true) + { + StringBuilder sb = new StringBuilder(); + for (int termIDX = 0; termIDX < 100; termIDX++) + { + sb.Append(' ').Append(TestUtil.RandomRealisticUnicodeString(Random())); + } + if (id == 500) + { + w.DeleteDocuments(new Term("id", "0")); + } + Document doc = new Document(); + doc.Add(NewStringField("id", "" + id, Field.Store.NO)); + doc.Add(NewTextField("body", sb.ToString(), Field.Store.NO)); + w.UpdateDocument(new Term("id", "" + id), doc); + docsInSegment.IncrementAndGet(); + // TODO: fix this test + if (SlowFileExists(dir, "_0_1.del") || SlowFileExists(dir, "_0_1.liv")) + { + if (VERBOSE) + { + Console.WriteLine("TEST: deletes created @ id=" + id); + } + break; + } + id++; + } + closing.Set(true); + Assert.IsTrue(sawAfterFlush.Get()); + w.Dispose(); + dir.Dispose(); + } + + private class IndexWriterAnonymousInnerClassHelper : IndexWriter + { + private readonly TestIndexWriterDelete OuterInstance; + + private AtomicInt32 DocsInSegment; + private AtomicBoolean Closing; + private AtomicBoolean SawAfterFlush; + + public IndexWriterAnonymousInnerClassHelper(TestIndexWriterDelete outerInstance, Directory dir, IndexWriterConfig setReaderPooling, AtomicInt32 docsInSegment, AtomicBoolean closing, AtomicBoolean sawAfterFlush) + : base(dir, setReaderPooling) + { + this.OuterInstance = outerInstance; + this.DocsInSegment = docsInSegment; + this.Closing = closing; + this.SawAfterFlush = sawAfterFlush; + } + + protected override void DoAfterFlush() + { + Assert.IsTrue(Closing.Get() || DocsInSegment.Get() >= 7, "only " + DocsInSegment.Get() + " in segment"); + DocsInSegment.Set(0); + SawAfterFlush.Set(true); + } + } + + // LUCENE-4455 + [Test] + public virtual void TestDeletesCheckIndexOutput() + { + Directory dir = NewDirectory(); + IndexWriterConfig iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())); + iwc.SetMaxBufferedDocs(2); + IndexWriter w = new IndexWriter(dir, (IndexWriterConfig)iwc.Clone()); + Document doc = new Document(); + doc.Add(NewField("field", "0", StringField.TYPE_NOT_STORED)); + w.AddDocument(doc); + + doc = new Document(); + doc.Add(NewField("field", "1", StringField.TYPE_NOT_STORED)); + w.AddDocument(doc); + w.Commit(); + Assert.AreEqual(1, w.SegmentCount); + + w.DeleteDocuments(new Term("field", "0")); + w.Commit(); + Assert.AreEqual(1, w.SegmentCount); + w.Dispose(); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); + //MemoryStream bos = new MemoryStream(1024); + CheckIndex checker = new CheckIndex(dir); + checker.InfoStream = new StreamWriter(bos, Encoding.UTF8); + CheckIndex.Status indexStatus = checker.DoCheckIndex(null); + Assert.IsTrue(indexStatus.Clean); + checker.FlushInfoStream(); + string s = bos.ToString(); + + // Segment should have deletions: + Assert.IsTrue(s.Contains("has deletions"), "string was: " + s); + w = new IndexWriter(dir, (IndexWriterConfig)iwc.Clone()); + w.ForceMerge(1); + w.Dispose(); + + bos = new ByteArrayOutputStream(1024); + checker.InfoStream = new StreamWriter(bos, Encoding.UTF8); + indexStatus = checker.DoCheckIndex(null); + Assert.IsTrue(indexStatus.Clean); + checker.FlushInfoStream(); + s = bos.ToString(); + Assert.IsFalse(s.Contains("has deletions")); + dir.Dispose(); + } + + [Test] + public virtual void TestTryDeleteDocument() + { + Directory d = NewDirectory(); + + IndexWriterConfig iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())); + IndexWriter w = new IndexWriter(d, iwc); + Document doc = new Document(); + w.AddDocument(doc); + w.AddDocument(doc); + w.AddDocument(doc); + w.Dispose(); + + iwc = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())); + iwc.SetOpenMode(OpenMode.APPEND); + w = new IndexWriter(d, iwc); + IndexReader r = DirectoryReader.Open(w, false); + Assert.IsTrue(w.TryDeleteDocument(r, 1)); + Assert.IsTrue(w.TryDeleteDocument(r.Leaves[0].Reader, 0)); + r.Dispose(); + w.Dispose(); + + r = DirectoryReader.Open(d); + Assert.AreEqual(2, r.NumDeletedDocs); + Assert.IsNotNull(MultiFields.GetLiveDocs(r)); + r.Dispose(); + d.Dispose(); + } + } +} \ No newline at end of file