You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2017/08/18 08:04:49 UTC

[02/20] lucenenet git commit: LUCENENET-565: Porting of Lucene Replicator - Commit is for Review with comments about original Java Source for assistance.

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyReplicationClientTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyReplicationClientTest.cs b/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyReplicationClientTest.cs
new file mode 100644
index 0000000..645888a
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyReplicationClientTest.cs
@@ -0,0 +1,518 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Lucene.Net.Documents;
+using Lucene.Net.Facet;
+using Lucene.Net.Facet.Taxonomy;
+using Lucene.Net.Facet.Taxonomy.Directory;
+using Lucene.Net.Index;
+using Lucene.Net.Replicator;
+using Lucene.Net.Search;
+using Lucene.Net.Store;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using Directory = Lucene.Net.Store.Directory;
+
+namespace Lucene.Net.Tests.Replicator
+{
+    [TestFixture]
+    public class IndexAndTaxonomyReplicationClientTest : ReplicatorTestCase
+    {
+        private class IndexAndTaxonomyReadyCallback : IDisposable
+        {
+            private Directory indexDir, taxoDir;
+            private DirectoryReader indexReader;
+            private DirectoryTaxonomyReader taxoReader;
+            private FacetsConfig config;
+            private long lastIndexGeneration = -1;
+
+            public IndexAndTaxonomyReadyCallback(MockDirectoryWrapper indexDir, MockDirectoryWrapper taxoDir)
+            {
+                this.indexDir = indexDir;
+                this.taxoDir = taxoDir;
+                config = new FacetsConfig();
+                config.SetHierarchical("A", true);
+                if (DirectoryReader.IndexExists(indexDir))
+                {
+                    indexReader = DirectoryReader.Open(indexDir);
+                    lastIndexGeneration = indexReader.IndexCommit.Generation;
+                    taxoReader = new DirectoryTaxonomyReader(taxoDir);
+                }
+            }
+
+            public bool? Call()
+            {
+                if (indexReader == null)
+                {
+                    indexReader = DirectoryReader.Open(indexDir);
+                    lastIndexGeneration = indexReader.IndexCommit.Generation;
+                    taxoReader = new DirectoryTaxonomyReader(taxoDir);
+                }
+                else
+                {
+                    // verify search index
+                    DirectoryReader newReader = DirectoryReader.OpenIfChanged(indexReader);
+                    assertNotNull("should not have reached here if no changes were made to the index", newReader);
+                    long newGeneration = newReader.IndexCommit.Generation;
+                    assertTrue("expected newer generation; current=" + lastIndexGeneration + " new=" + newGeneration, newGeneration > lastIndexGeneration);
+                    indexReader.Dispose();
+                    indexReader = newReader;
+                    lastIndexGeneration = newGeneration;
+                    TestUtil.CheckIndex(indexDir);
+
+                    // verify taxonomy index
+                    DirectoryTaxonomyReader newTaxoReader = TaxonomyReader.OpenIfChanged(taxoReader);
+                    if (newTaxoReader != null)
+                    {
+                        taxoReader.Dispose();
+                        taxoReader = newTaxoReader;
+                    }
+                    TestUtil.CheckIndex(taxoDir);
+
+                    // verify faceted search
+                    int id = int.Parse(indexReader.IndexCommit.UserData[VERSION_ID], NumberStyles.HexNumber);
+                    IndexSearcher searcher = new IndexSearcher(indexReader);
+                    FacetsCollector fc = new FacetsCollector();
+                    searcher.Search(new MatchAllDocsQuery(), fc);
+                    Facets facets = new FastTaxonomyFacetCounts(taxoReader, config, fc);
+                    assertEquals(1, (int)facets.GetSpecificValue("A", id.ToString("X")));
+
+                    DrillDownQuery drillDown = new DrillDownQuery(config);
+                    drillDown.Add("A", id.ToString("X"));
+                    TopDocs docs = searcher.Search(drillDown, 10);
+                    assertEquals(1, docs.TotalHits);
+                }
+                return null;
+            }
+
+            public void Dispose()
+            {
+                IOUtils.Dispose(indexReader, taxoReader);
+            }
+        }
+
+        private Directory publishIndexDir, publishTaxoDir;
+        private MockDirectoryWrapper handlerIndexDir, handlerTaxoDir;
+        private IReplicator replicator;
+        private ISourceDirectoryFactory sourceDirFactory;
+        private ReplicationClient client;
+        private IReplicationHandler handler;
+        private IndexWriter publishIndexWriter;
+        private IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter publishTaxoWriter;
+        private FacetsConfig config;
+        private IndexAndTaxonomyReadyCallback callback;
+        private DirectoryInfo clientWorkDir;
+
+        private const string VERSION_ID = "version";
+
+        private void AssertHandlerRevision(int expectedId, Directory dir)
+        {
+            //JAVA: private void assertHandlerRevision(int expectedID, Directory dir) throws IOException {
+            //JAVA:   // loop as long as client is alive. test-framework will terminate us if
+            //JAVA:   // there's a serious bug, e.g. client doesn't really update. otherwise,
+            //JAVA:   // introducing timeouts is not good, can easily lead to false positives.
+            //JAVA:   while (client.isUpdateThreadAlive()) {
+            //JAVA:     // give client a chance to update
+            //JAVA:     try {
+            //JAVA:       Thread.sleep(100);
+            //JAVA:     } catch (InterruptedException e) {
+            //JAVA:       throw new ThreadInterruptedException(e);
+            //JAVA:     }
+            //JAVA:     
+            //JAVA:     try {
+            //JAVA:       DirectoryReader reader = DirectoryReader.open(dir);
+            //JAVA:       try {
+            //JAVA:         int handlerID = Integer.parseInt(reader.getIndexCommit().getUserData().get(VERSION_ID), 16);
+            //JAVA:         if (expectedID == handlerID) {
+            //JAVA:           return;
+            //JAVA:         }
+            //JAVA:       } finally {
+            //JAVA:         reader.close();
+            //JAVA:       }
+            //JAVA:     } catch (Exception e) {
+            //JAVA:       // we can hit IndexNotFoundException or e.g. EOFException (on
+            //JAVA:       // segments_N) because it is being copied at the same time it is read by
+            //JAVA:       // DirectoryReader.open().
+            //JAVA:     }
+            //JAVA:   }
+            //JAVA: }
+
+            // loop as long as client is alive. test-framework will terminate us if
+            // there's a serious bug, e.g. client doesn't really update. otherwise,
+            // introducing timeouts is not good, can easily lead to false positives.
+            while (client.IsUpdateThreadAlive)
+            {
+                Thread.Sleep(100);
+
+                try
+                {
+                    DirectoryReader reader = DirectoryReader.Open(dir);
+                    try
+                    {
+                        int handlerId = int.Parse(reader.IndexCommit.UserData[VERSION_ID], NumberStyles.HexNumber);
+                        if (expectedId == handlerId)
+                        {
+                            return;
+                        }
+                    }
+                    finally
+                    {
+                        reader.Dispose();
+                    }
+                }
+                catch (Exception)
+                {
+                    // we can hit IndexNotFoundException or e.g. EOFException (on
+                    // segments_N) because it is being copied at the same time it is read by
+                    // DirectoryReader.open().
+                }
+            }
+        }
+
+        private IRevision CreateRevision(int id)
+        {
+            //JAVA: private Revision createRevision(final int id) throws IOException {
+            //JAVA:   publishIndexWriter.addDocument(newDocument(publishTaxoWriter, id));
+            //JAVA:   publishIndexWriter.setCommitData(new HashMap<String, String>() {{
+            //JAVA:     put(VERSION_ID, Integer.toString(id, 16));
+            //JAVA:   }});
+            //JAVA:   publishIndexWriter.commit();
+            //JAVA:   publishTaxoWriter.commit();
+            //JAVA:   return new IndexAndTaxonomyRevision(publishIndexWriter, publishTaxoWriter);
+            //JAVA: }
+            publishIndexWriter.AddDocument(NewDocument(publishTaxoWriter, id));
+            publishIndexWriter.SetCommitData(new Dictionary<string, string>{
+                { VERSION_ID, id.ToString("X") }
+            });
+            publishIndexWriter.Commit();
+            publishTaxoWriter.Commit();
+            return new IndexAndTaxonomyRevision(publishIndexWriter, publishTaxoWriter);
+        }
+
+        private Document NewDocument(ITaxonomyWriter taxoWriter, int id)
+        {
+            Document doc = new Document();
+            doc.Add(new FacetField("A", id.ToString("X")));
+            return config.Build(taxoWriter, doc);
+        }
+
+        public override void SetUp()
+        {
+            base.SetUp();
+
+            publishIndexDir = NewDirectory();
+            publishTaxoDir = NewDirectory();
+            handlerIndexDir = NewMockDirectory();
+            handlerTaxoDir = NewMockDirectory();
+            clientWorkDir = CreateTempDir("replicationClientTest");
+            sourceDirFactory = new PerSessionDirectoryFactory(clientWorkDir.FullName);
+            replicator = new LocalReplicator();
+            callback = new IndexAndTaxonomyReadyCallback(handlerIndexDir, handlerTaxoDir);
+            handler = new IndexAndTaxonomyReplicationHandler(handlerIndexDir, handlerTaxoDir, callback.Call);
+            client = new ReplicationClient(replicator, handler, sourceDirFactory);
+
+            IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            publishIndexWriter = new IndexWriter(publishIndexDir, conf);
+            publishTaxoWriter = new IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter(publishTaxoDir);
+            config = new FacetsConfig();
+            config.SetHierarchical("A", true);
+        }
+
+        public override void TearDown()
+        {
+            IOUtils.Dispose(client, callback, publishIndexWriter, publishTaxoWriter, replicator, publishIndexDir, publishTaxoDir,
+                handlerIndexDir, handlerTaxoDir);
+            base.TearDown();
+        }
+
+        [Test]
+        public void TestNoUpdateThread()
+        {
+            assertNull("no version expected at start", handler.CurrentVersion);
+
+            // Callback validates the replicated index
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+
+            // make sure updating twice, when in fact there's nothing to update, works
+            client.UpdateNow();
+
+            replicator.Publish(CreateRevision(2));
+            client.UpdateNow();
+
+            // Publish two revisions without update, handler should be upgraded to latest
+            replicator.Publish(CreateRevision(3));
+            replicator.Publish(CreateRevision(4));
+            client.UpdateNow();
+        }
+
+        [Test]
+        public void TestRestart()
+        {
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+
+            replicator.Publish(CreateRevision(2));
+            client.UpdateNow();
+
+            client.StopUpdateThread();
+            client.Dispose();
+            client = new ReplicationClient(replicator, handler, sourceDirFactory);
+
+            // Publish two revisions without update, handler should be upgraded to latest
+            replicator.Publish(CreateRevision(3));
+            replicator.Publish(CreateRevision(4));
+            client.UpdateNow();
+        }
+
+        [Test]
+        public void TestUpdateThread()
+        {
+            client.StartUpdateThread(10, "indexTaxo");
+
+            replicator.Publish(CreateRevision(1));
+            AssertHandlerRevision(1, handlerIndexDir);
+
+            replicator.Publish(CreateRevision(2));
+            AssertHandlerRevision(2, handlerIndexDir);
+
+            // Publish two revisions without update, handler should be upgraded to latest
+            replicator.Publish(CreateRevision(3));
+            replicator.Publish(CreateRevision(4));
+            AssertHandlerRevision(4, handlerIndexDir);
+        }
+
+        [Test]
+        public void TestRecreateTaxonomy()
+        {
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+
+            // recreate index and taxonomy
+            Directory newTaxo = NewDirectory();
+            new DirectoryTaxonomyWriter(newTaxo).Dispose();
+            publishTaxoWriter.ReplaceTaxonomy(newTaxo);
+            publishIndexWriter.DeleteAll();
+            replicator.Publish(CreateRevision(2));
+
+            client.UpdateNow();
+            newTaxo.Dispose();
+        }
+
+        //JAVA: /*
+        //JAVA:  * This test verifies that the client and handler do not end up in a corrupt
+        //JAVA:  * index if exceptions are thrown at any point during replication. Either when
+        //JAVA:  * a client copies files from the server to the temporary space, or when the
+        //JAVA:  * handler copies them to the index directory.
+        //JAVA:  */
+        [Test]
+        public void TestConsistencyOnExceptions()
+        {
+            // so the handler's index isn't empty
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+            client.Dispose();
+            callback.Dispose();
+
+            // Replicator violates write-once policy. It may be that the
+            // handler copies files to the index dir, then fails to copy a
+            // file and reverts the copy operation. On the next attempt, it
+            // will copy the same file again. There is nothing wrong with this
+            // in a real system, but it does violate write-once, and MDW
+            // doesn't like it. Disabling it means that we won't catch cases
+            // where the handler overwrites an existing index file, but
+            // there's nothing currently we can do about it, unless we don't
+            // use MDW.
+            handlerIndexDir.PreventDoubleWrite=(false);
+            handlerTaxoDir.PreventDoubleWrite = (false);
+
+            // wrap sourceDirFactory to return a MockDirWrapper so we can simulate errors
+            ISourceDirectoryFactory @in = sourceDirFactory;
+            AtomicInt32 failures = new AtomicInt32(AtLeast(10));
+
+            sourceDirFactory = new SourceDirectoryFactoryAnonymousInnerClass(this, @in, failures);
+            handler = new IndexAndTaxonomyReplicationHandler(handlerIndexDir, handlerTaxoDir, () =>
+            {
+                if (Random().NextDouble() < 0.2 && failures.Get() > 0)
+                    throw new Exception("random exception from callback");
+                return null;
+            });
+            client = new ReplicationClientAnonymousInnerClass(this, replicator, handler, @in, failures);
+            client.StartUpdateThread(10, "indexAndTaxo");
+
+            Directory baseHandlerIndexDir = handlerIndexDir.Delegate;
+            int numRevisions = AtLeast(20) + 2;
+            for (int i = 2; i < numRevisions; i++)
+            {
+                replicator.Publish(CreateRevision(i));
+                AssertHandlerRevision(i, baseHandlerIndexDir);
+            }
+
+            // disable errors -- maybe randomness didn't exhaust all allowed failures,
+            // and we don't want e.g. CheckIndex to hit false errors. 
+            handlerIndexDir.MaxSizeInBytes=(0);
+            handlerIndexDir.RandomIOExceptionRate=(0.0);
+            handlerIndexDir.RandomIOExceptionRateOnOpen=(0.0);
+            handlerTaxoDir.MaxSizeInBytes=(0);
+            handlerTaxoDir.RandomIOExceptionRate=(0.0);
+            handlerTaxoDir.RandomIOExceptionRateOnOpen=(0.0);
+        }
+
+        private class SourceDirectoryFactoryAnonymousInnerClass : ISourceDirectoryFactory
+        {
+            private long clientMaxSize = 100, handlerIndexMaxSize = 100, handlerTaxoMaxSize = 100;
+            private double clientExRate = 1.0, handlerIndexExRate = 1.0, handlerTaxoExRate = 1.0;
+
+            private readonly IndexAndTaxonomyReplicationClientTest test;
+            private readonly ISourceDirectoryFactory @in;
+            private readonly AtomicInt32 failures;
+
+            public SourceDirectoryFactoryAnonymousInnerClass(IndexAndTaxonomyReplicationClientTest test, ISourceDirectoryFactory @in, AtomicInt32 failures)
+            {
+                this.test = test;
+                this.@in = @in;
+                this.failures = failures;
+            }
+
+            public void CleanupSession(string sessionId)
+            {
+                @in.CleanupSession(sessionId);
+            }
+
+            public Directory GetDirectory(string sessionId, string source)
+            {
+                Directory dir = @in.GetDirectory(sessionId, source);
+                if (Random().nextBoolean() && failures.Get() > 0)
+                { // client should fail, return wrapped dir
+                    MockDirectoryWrapper mdw = new MockDirectoryWrapper(Random(), dir);
+                    mdw.RandomIOExceptionRateOnOpen = clientExRate;
+                    mdw.MaxSizeInBytes = clientMaxSize;
+                    mdw.RandomIOExceptionRate = clientExRate;
+                    mdw.CheckIndexOnClose = false;
+                    clientMaxSize *= 2;
+                    clientExRate /= 2;
+                    return mdw;
+                }
+
+                if (failures.Get() > 0 && Random().nextBoolean())
+                { // handler should fail
+                    if (Random().nextBoolean())
+                    { // index dir fail
+                        test.handlerIndexDir.MaxSizeInBytes=(handlerIndexMaxSize);
+                        test.handlerIndexDir.RandomIOExceptionRate = (handlerIndexExRate);
+                        test.handlerIndexDir.RandomIOExceptionRateOnOpen = (handlerIndexExRate);
+                        handlerIndexMaxSize *= 2;
+                        handlerIndexExRate /= 2;
+                    }
+                    else
+                    { // taxo dir fail
+                        test.handlerTaxoDir.MaxSizeInBytes = (handlerTaxoMaxSize);
+                        test.handlerTaxoDir.RandomIOExceptionRate = (handlerTaxoExRate);
+                        test.handlerTaxoDir.RandomIOExceptionRateOnOpen = (handlerTaxoExRate);
+                        test.handlerTaxoDir.CheckIndexOnClose = (false);
+                        handlerTaxoMaxSize *= 2;
+                        handlerTaxoExRate /= 2;
+                    }
+                }
+                else
+                {
+                    // disable all errors
+                    test.handlerIndexDir.MaxSizeInBytes = (0);
+                    test.handlerIndexDir.RandomIOExceptionRate = (0.0);
+                    test.handlerIndexDir.RandomIOExceptionRateOnOpen = (0.0);
+                    test.handlerTaxoDir.MaxSizeInBytes = (0);
+                    test.handlerTaxoDir.RandomIOExceptionRate = (0.0);
+                    test.handlerTaxoDir.RandomIOExceptionRateOnOpen = (0.0);
+                }
+                return dir;
+            }
+        }
+
+
+
+        private class ReplicationClientAnonymousInnerClass : ReplicationClient
+        {
+            private readonly IndexAndTaxonomyReplicationClientTest test;
+            private readonly AtomicInt32 failures;
+
+            public ReplicationClientAnonymousInnerClass(IndexAndTaxonomyReplicationClientTest test, IReplicator replicator, IReplicationHandler handler, ISourceDirectoryFactory factory, AtomicInt32 failures)
+                : base(replicator, handler, factory)
+            {
+                this.test = test;
+                this.failures = failures;
+            }
+
+            protected override void HandleUpdateException(Exception exception)
+            {
+                if (exception is IOException)
+                {
+                    try
+                    {
+                        if (VERBOSE)
+                        {
+                            Console.WriteLine("hit exception during update: " + exception);
+                        }
+
+                        // test that the index can be read and also some basic statistics
+                        DirectoryReader reader = DirectoryReader.Open(test.handlerIndexDir.Delegate);
+                        try
+                        {
+                            int numDocs = reader.NumDocs;
+                            int version = int.Parse(reader.IndexCommit.UserData[VERSION_ID], NumberStyles.HexNumber);
+                            assertEquals(numDocs, version);
+                        }
+                        finally
+                        {
+                            reader.Dispose();
+                        }
+                        // verify index consistency
+                        TestUtil.CheckIndex(test.handlerIndexDir.Delegate);
+
+                        // verify taxonomy index is fully consistent (since we only add one
+                        // category to all documents, there's nothing much more to validate
+                        TestUtil.CheckIndex(test.handlerTaxoDir.Delegate);
+                    }
+                    //TODO: Java had this, but considering what it does do we need it?
+                    //JAVA: catch (IOException e)
+                    //JAVA: {
+                    //JAVA:     throw new RuntimeException(e);
+                    //JAVA: }
+                    finally
+                    {
+                        // count-down number of failures
+                        failures.DecrementAndGet();
+                        Debug.Assert(failures.Get() >= 0, "handler failed too many times: " + failures.Get());
+                        if (VERBOSE)
+                        {
+                            if (failures.Get() == 0)
+                            {
+                                Console.WriteLine("no more failures expected");
+                            }
+                            else
+                            {
+                                Console.WriteLine("num failures left: " + failures.Get());
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    //JAVA:          if (t instanceof RuntimeException) throw (RuntimeException) t;
+                    //JAVA:          throw new RuntimeException(t);
+                    throw exception;
+                }
+            }
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyRevisionTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyRevisionTest.cs b/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyRevisionTest.cs
new file mode 100644
index 0000000..dd20864
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/IndexAndTaxonomyRevisionTest.cs
@@ -0,0 +1,188 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Lucene.Net.Documents;
+using Lucene.Net.Facet;
+using Lucene.Net.Facet.Taxonomy;
+using Lucene.Net.Index;
+using Lucene.Net.Replicator;
+using Lucene.Net.Store;
+using Lucene.Net.Util;
+using NUnit.Framework;
+
+namespace Lucene.Net.Tests.Replicator
+{
+    /*
+	 * 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.
+	 */
+
+    public class IndexAndTaxonomyRevisionTest : ReplicatorTestCase
+    {
+        private Document NewDocument(ITaxonomyWriter taxoWriter)
+        {
+            FacetsConfig config = new FacetsConfig();
+            Document doc = new Document();
+            doc.Add(new FacetField("A", "1"));
+            return config.Build(taxoWriter, doc);
+        }
+
+        [Test]
+        public void TestNoCommit()
+        {
+            Directory indexDir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter indexWriter = new IndexWriter(indexDir, conf);
+
+            Directory taxoDir = NewDirectory();
+            IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter taxoWriter = new IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter(taxoDir);
+            try
+            {
+                assertNotNull(new IndexAndTaxonomyRevision(indexWriter, taxoWriter));
+                fail("should have failed when there are no commits to snapshot");
+            }
+            catch (System.InvalidOperationException)
+            {
+                // expected
+            }
+            finally
+            {
+                IOUtils.Dispose(indexWriter, taxoWriter, taxoDir, indexDir);
+            }
+        }
+
+        [Test]
+        public void TestRevisionRelease()
+        {
+            Directory indexDir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter indexWriter = new IndexWriter(indexDir, conf);
+
+            Directory taxoDir = NewDirectory();
+            IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter taxoWriter = new IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter(taxoDir);
+            try
+            {
+                indexWriter.AddDocument(NewDocument(taxoWriter));
+                indexWriter.Commit();
+                taxoWriter.Commit();
+                IRevision rev1 = new IndexAndTaxonomyRevision(indexWriter, taxoWriter);
+                // releasing that revision should not delete the files
+                rev1.Release();
+                assertTrue(SlowFileExists(indexDir, IndexFileNames.SEGMENTS + "_1"));
+                assertTrue(SlowFileExists(taxoDir, IndexFileNames.SEGMENTS + "_1"));
+
+                rev1 = new IndexAndTaxonomyRevision(indexWriter, taxoWriter); // create revision again, so the files are snapshotted
+                indexWriter.AddDocument(NewDocument(taxoWriter));
+                indexWriter.Commit();
+                taxoWriter.Commit();
+                assertNotNull(new IndexAndTaxonomyRevision(indexWriter, taxoWriter));
+                rev1.Release(); // this release should trigger the delete of segments_1
+                assertFalse(SlowFileExists(indexDir, IndexFileNames.SEGMENTS + "_1"));
+            }
+            finally
+            {
+                IOUtils.Dispose(indexWriter, taxoWriter, taxoDir, indexDir);
+            }
+        }
+
+        [Test]
+        public void TestSegmentsFileLast()
+        {
+            Directory indexDir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter indexWriter = new IndexWriter(indexDir, conf);
+
+            Directory taxoDir = NewDirectory();
+            IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter taxoWriter = new IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter(taxoDir);
+            try
+            {
+                indexWriter.AddDocument(NewDocument(taxoWriter));
+                indexWriter.Commit();
+                taxoWriter.Commit();
+                IRevision rev = new IndexAndTaxonomyRevision(indexWriter, taxoWriter);
+                var sourceFiles = rev.SourceFiles;
+                assertEquals(2, sourceFiles.Count);
+                foreach (var files in sourceFiles.Values)
+                {
+                    string lastFile = files.Last().FileName;
+                    assertTrue(lastFile.StartsWith(IndexFileNames.SEGMENTS, StringComparison.Ordinal) && !lastFile.Equals(IndexFileNames.SEGMENTS_GEN));
+                }
+            }
+            finally
+            {
+                IOUtils.Dispose(indexWriter, taxoWriter, taxoDir, indexDir);
+            }
+        }
+
+        [Test]
+        public void TestOpen()
+        {
+            Directory indexDir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter indexWriter = new IndexWriter(indexDir, conf);
+
+            Directory taxoDir = NewDirectory();
+            IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter taxoWriter = new IndexAndTaxonomyRevision.SnapshotDirectoryTaxonomyWriter(taxoDir);
+            try
+            {
+                indexWriter.AddDocument(NewDocument(taxoWriter));
+                indexWriter.Commit();
+                taxoWriter.Commit();
+                IRevision rev = new IndexAndTaxonomyRevision(indexWriter, taxoWriter);
+                foreach (var e in rev.SourceFiles)
+                {
+                    string source = e.Key;
+                    Directory dir = source.Equals(IndexAndTaxonomyRevision.INDEX_SOURCE) ? indexDir : taxoDir;
+                    foreach (RevisionFile file in e.Value)
+                    {
+                        IndexInput src = dir.OpenInput(file.FileName, IOContext.READ_ONCE);
+                        System.IO.Stream @in = rev.Open(source, file.FileName);
+                        assertEquals(src.Length, @in.Length);
+                        byte[] srcBytes = new byte[(int)src.Length];
+                        byte[] inBytes = new byte[(int)src.Length];
+                        int offset = 0;
+                        if (Random().nextBoolean())
+                        {
+                            int skip = Random().Next(10);
+                            if (skip >= src.Length)
+                            {
+                                skip = 0;
+                            }
+                            //JAVA: in.skip(skip);
+                            byte[] skips = new byte[skip];
+                            @in.Read(skips, 0, skip);
+                            src.Seek(skip);
+                            offset = skip;
+                        }
+                        src.ReadBytes(srcBytes, offset, srcBytes.Length - offset);
+                        @in.Read(inBytes, offset, inBytes.Length - offset);
+                        assertArrayEquals(srcBytes, inBytes);
+                        IOUtils.Dispose(src, @in);
+                    }
+                }
+            }
+            finally
+            {
+                IOUtils.Dispose(indexWriter, taxoWriter, taxoDir, indexDir);
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/IndexReplicationClientTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/IndexReplicationClientTest.cs b/src/Lucene.Net.Tests.Replicator/IndexReplicationClientTest.cs
new file mode 100644
index 0000000..6a56c77
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/IndexReplicationClientTest.cs
@@ -0,0 +1,513 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Replicator;
+using Lucene.Net.Store;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using Directory = Lucene.Net.Store.Directory;
+
+namespace Lucene.Net.Tests.Replicator
+{
+    [TestFixture]
+    public class IndexReplicationClientTest : ReplicatorTestCase
+    {
+        private class IndexReadyCallback : IDisposable
+        {
+            private readonly Directory indexDir;
+            private DirectoryReader reader;
+            private long lastGeneration = -1;
+
+            public IndexReadyCallback(Directory indexDir)
+            {
+                //JAVA:    public IndexReadyCallback(Directory indexDir) throws IOException {
+                //JAVA:      this.indexDir = indexDir;
+                //JAVA:      if (DirectoryReader.indexExists(indexDir)) {
+                //JAVA:        reader = DirectoryReader.open(indexDir);
+                //JAVA:        lastGeneration = reader.getIndexCommit().getGeneration();
+                //JAVA:      }
+                //JAVA:    }
+
+                this.indexDir = indexDir;
+                if (DirectoryReader.IndexExists(indexDir))
+                {
+                    reader = DirectoryReader.Open(indexDir);
+                    lastGeneration = reader.IndexCommit.Generation;
+                }
+            }
+
+            public bool? Call()
+            {
+                //JAVA:    public Boolean call() throws Exception {
+                //JAVA:      if (reader == null) {
+                //JAVA:        reader = DirectoryReader.open(indexDir);
+                //JAVA:        lastGeneration = reader.getIndexCommit().getGeneration();
+                //JAVA:      } else {
+                //JAVA:        DirectoryReader newReader = DirectoryReader.openIfChanged(reader);
+                //JAVA:        assertNotNull("should not have reached here if no changes were made to the index", newReader);
+                //JAVA:        long newGeneration = newReader.getIndexCommit().getGeneration();
+                //JAVA:        assertTrue("expected newer generation; current=" + lastGeneration + " new=" + newGeneration, newGeneration > lastGeneration);
+                //JAVA:        reader.close();
+                //JAVA:        reader = newReader;
+                //JAVA:        lastGeneration = newGeneration;
+                //JAVA:        TestUtil.checkIndex(indexDir);
+                //JAVA:      }
+                //JAVA:      return null;
+                //JAVA:    }
+                if (reader == null)
+                {
+                    reader = DirectoryReader.Open(indexDir);
+                    lastGeneration = reader.IndexCommit.Generation;
+                }
+                else
+                {
+                    DirectoryReader newReader = DirectoryReader.OpenIfChanged(reader);
+                    assertNotNull("should not have reached here if no changes were made to the index", newReader);
+                    long newGeneration = newReader.IndexCommit.Generation;
+                    assertTrue("expected newer generation; current=" + lastGeneration + " new=" + newGeneration, newGeneration > lastGeneration);
+                    reader.Dispose();
+                    reader = newReader;
+                    lastGeneration = newGeneration;
+                    TestUtil.CheckIndex(indexDir);
+                }
+                return null;
+            }
+            public void Dispose()
+            {
+                IOUtils.Dispose(reader);
+            }
+        }
+
+
+        private MockDirectoryWrapper publishDir, handlerDir;
+        private IReplicator replicator;
+        private ISourceDirectoryFactory sourceDirFactory;
+        private ReplicationClient client;
+        private IReplicationHandler handler;
+        private IndexWriter publishWriter;
+        private IndexReadyCallback callback;
+        //JAVA:  private IndexReadyCallback callback;
+
+        private const string VERSION_ID = "version";
+
+        private void AssertHandlerRevision(int expectedId, Directory dir)
+        {
+            //JAVA:  private void assertHandlerRevision(int expectedID, Directory dir) throws IOException {
+            //JAVA:    // loop as long as client is alive. test-framework will terminate us if
+            //JAVA:    // there's a serious bug, e.g. client doesn't really update. otherwise,
+            //JAVA:    // introducing timeouts is not good, can easily lead to false positives.
+            //JAVA:    while (client.isUpdateThreadAlive()) {
+            //JAVA:      // give client a chance to update
+            //JAVA:      try {
+            //JAVA:        Thread.sleep(100);
+            //JAVA:      } catch (InterruptedException e) {
+            //JAVA:        throw new ThreadInterruptedException(e);
+            //JAVA:      }
+            //JAVA:
+            //JAVA:      try {
+            //JAVA:        DirectoryReader reader = DirectoryReader.open(dir);
+            //JAVA:        try {
+            //JAVA:          int handlerID = Integer.parseInt(reader.getIndexCommit().getUserData().get(VERSION_ID), 16);
+            //JAVA:          if (expectedID == handlerID) {
+            //JAVA:            return;
+            //JAVA:          } else if (VERBOSE) {
+            //JAVA:            System.out.println("expectedID=" + expectedID + " actual=" + handlerID + " generation=" + reader.getIndexCommit().getGeneration());
+            //JAVA:          }
+            //JAVA:        } finally {
+            //JAVA:          reader.close();
+            //JAVA:        }
+            //JAVA:      } catch (Exception e) {
+            //JAVA:        // we can hit IndexNotFoundException or e.g. EOFException (on
+            //JAVA:        // segments_N) because it is being copied at the same time it is read by
+            //JAVA:        // DirectoryReader.open().
+            //JAVA:      }
+            //JAVA:    }
+            //JAVA:  }
+
+            // loop as long as client is alive. test-framework will terminate us if
+            // there's a serious bug, e.g. client doesn't really update. otherwise,
+            // introducing timeouts is not good, can easily lead to false positives.
+            while (client.IsUpdateThreadAlive)
+            {
+                // give client a chance to update
+                Thread.Sleep(100);
+                try
+                {
+                    DirectoryReader reader = DirectoryReader.Open(dir);
+                    try
+                    {
+                        int handlerId = int.Parse(reader.IndexCommit.UserData[VERSION_ID], NumberStyles.HexNumber);
+                        if (expectedId == handlerId)
+                        {
+                            return;
+                        }
+                        else if (VERBOSE)
+                        {
+                            Console.WriteLine("expectedID=" + expectedId + " actual=" + handlerId + " generation=" + reader.IndexCommit.Generation);
+                        }
+                    }
+                    finally
+                    {
+                        reader.Dispose();
+                    }
+                }
+                catch (Exception)
+                {
+                    // we can hit IndexNotFoundException or e.g. EOFException (on
+                    // segments_N) because it is being copied at the same time it is read by
+                    // DirectoryReader.open().
+                }
+            }
+        }
+
+        private IRevision CreateRevision(int id)
+        {
+            //JAVA:  private Revision createRevision(final int id) throws IOException {
+            //JAVA:    publishWriter.addDocument(new Document());
+            //JAVA:    publishWriter.setCommitData(new HashMap<String, String>() {{
+            //JAVA:      put(VERSION_ID, Integer.toString(id, 16));
+            //JAVA:    }});
+            //JAVA:    publishWriter.commit();
+            //JAVA:    return new IndexRevision(publishWriter);
+            //JAVA:  }
+            publishWriter.AddDocument(new Document());
+            publishWriter.SetCommitData(new Dictionary<string, string>{
+                { VERSION_ID, id.ToString("X") }
+            });
+            publishWriter.Commit();
+            return new IndexRevision(publishWriter);
+        }
+
+        public override void SetUp()
+        {
+            //JAVA:  public void setUp() throws Exception {
+            //JAVA:    super.setUp();
+            //JAVA:    publishDir = newMockDirectory();
+            //JAVA:    handlerDir = newMockDirectory();
+            //JAVA:    sourceDirFactory = new PerSessionDirectoryFactory(createTempDir("replicationClientTest"));
+            //JAVA:    replicator = new LocalReplicator();
+            //JAVA:    callback = new IndexReadyCallback(handlerDir);
+            //JAVA:    handler = new IndexReplicationHandler(handlerDir, callback);
+            //JAVA:    client = new ReplicationClient(replicator, handler, sourceDirFactory);
+            //JAVA:    
+            //JAVA:    IndexWriterConfig conf = newIndexWriterConfig(TEST_VERSION_CURRENT, null);
+            //JAVA:    conf.setIndexDeletionPolicy(new SnapshotDeletionPolicy(conf.getIndexDeletionPolicy()));
+            //JAVA:    publishWriter = new IndexWriter(publishDir, conf);
+            //JAVA:  }
+            base.SetUp();
+
+            publishDir = NewMockDirectory();
+            handlerDir = NewMockDirectory();
+            sourceDirFactory = new PerSessionDirectoryFactory(CreateTempDir("replicationClientTest").FullName);
+            replicator = new LocalReplicator();
+            callback = new IndexReadyCallback(handlerDir);
+            handler = new IndexReplicationHandler(handlerDir, callback.Call);
+            client = new ReplicationClient(replicator, handler, sourceDirFactory);
+
+            IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            publishWriter = new IndexWriter(publishDir, conf);
+        }
+
+        public override void TearDown()
+        {
+            //JAVA:  public void tearDown() throws Exception {
+            //JAVA:    IOUtils.close(client, callback, publishWriter, replicator, publishDir, handlerDir);
+            //JAVA:    super.tearDown();
+            //JAVA:  }
+            IOUtils.Dispose(client, callback, publishWriter, replicator, publishDir, handlerDir);
+            base.TearDown();
+        }
+
+        [Test]
+        public void TestNoUpdateThread()
+        {
+            //JAVA:  public void testNoUpdateThread() throws Exception {
+            //JAVA:    assertNull("no version expected at start", handler.currentVersion());
+            //JAVA:    
+            //JAVA:    // Callback validates the replicated index
+            //JAVA:    replicator.publish(createRevision(1));
+            //JAVA:    client.updateNow();
+            //JAVA:    
+            //JAVA:    replicator.publish(createRevision(2));
+            //JAVA:    client.updateNow();
+            //JAVA:    
+            //JAVA:    // Publish two revisions without update, handler should be upgraded to latest
+            //JAVA:    replicator.publish(createRevision(3));
+            //JAVA:    replicator.publish(createRevision(4));
+            //JAVA:    client.updateNow();
+            //JAVA:  }
+            assertNull("no version expected at start", handler.CurrentVersion);
+
+            // Callback validates the replicated ind
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+
+            replicator.Publish(CreateRevision(2));
+            client.UpdateNow();
+
+            // Publish two revisions without update,
+            replicator.Publish(CreateRevision(3));
+            replicator.Publish(CreateRevision(4));
+            client.UpdateNow();
+        }
+
+
+        [Test]
+        public void TestUpdateThread()
+        {
+            //JAVA:  public void testUpdateThread() throws Exception {
+            //JAVA:    client.startUpdateThread(10, "index");
+            //JAVA:    
+            //JAVA:    replicator.publish(createRevision(1));
+            //JAVA:    assertHandlerRevision(1, handlerDir);
+            //JAVA:    
+            //JAVA:    replicator.publish(createRevision(2));
+            //JAVA:    assertHandlerRevision(2, handlerDir);
+            //JAVA:    
+            //JAVA:    // Publish two revisions without update, handler should be upgraded to latest
+            //JAVA:    replicator.publish(createRevision(3));
+            //JAVA:    replicator.publish(createRevision(4));
+            //JAVA:    assertHandlerRevision(4, handlerDir);
+            //JAVA:  }
+
+            client.StartUpdateThread(10, "index");
+
+            replicator.Publish(CreateRevision(1));
+            AssertHandlerRevision(1, handlerDir);
+
+            replicator.Publish(CreateRevision(2));
+            AssertHandlerRevision(2, handlerDir);
+
+            // Publish two revisions without update, handler should be upgraded to latest
+            replicator.Publish(CreateRevision(3));
+            replicator.Publish(CreateRevision(4));
+            AssertHandlerRevision(4, handlerDir);
+        }
+
+        [Test]
+        public void TestRestart()
+        {
+            //JAVA:  public void testRestart() throws Exception {
+            //JAVA:    replicator.publish(createRevision(1));
+            //JAVA:    client.updateNow();
+            //JAVA:    
+            //JAVA:    replicator.publish(createRevision(2));
+            //JAVA:    client.updateNow();
+            //JAVA:    
+            //JAVA:    client.stopUpdateThread();
+            //JAVA:    client.close();
+            //JAVA:    client = new ReplicationClient(replicator, handler, sourceDirFactory);
+            //JAVA:    
+            //JAVA:    // Publish two revisions without update, handler should be upgraded to latest
+            //JAVA:    replicator.publish(createRevision(3));
+            //JAVA:    replicator.publish(createRevision(4));
+            //JAVA:    client.updateNow();
+            //JAVA:  }
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+
+            replicator.Publish(CreateRevision(2));
+            client.UpdateNow();
+
+            client.StopUpdateThread();
+            client.Dispose();
+            client = new ReplicationClient(replicator, handler, sourceDirFactory);
+
+            // Publish two revisions without update, handler should be upgraded to latest
+            replicator.Publish(CreateRevision(3));
+            replicator.Publish(CreateRevision(4));
+            client.UpdateNow();
+        }
+
+        //JAVA:  /*
+        //JAVA:   * This test verifies that the client and handler do not end up in a corrupt
+        //JAVA:   * index if exceptions are thrown at any point during replication. Either when
+        //JAVA:   * a client copies files from the server to the temporary space, or when the
+        //JAVA:   * handler copies them to the index directory.
+        //JAVA:   */
+        [Test]
+        public void TestConsistencyOnExceptions()
+        {
+            // so the handler's index isn't empty
+            replicator.Publish(CreateRevision(1));
+            client.UpdateNow();
+            client.Dispose();
+            callback.Dispose();
+
+            // Replicator violates write-once policy. It may be that the
+            // handler copies files to the index dir, then fails to copy a
+            // file and reverts the copy operation. On the next attempt, it
+            // will copy the same file again. There is nothing wrong with this
+            // in a real system, but it does violate write-once, and MDW
+            // doesn't like it. Disabling it means that we won't catch cases
+            // where the handler overwrites an existing index file, but
+            // there's nothing currently we can do about it, unless we don't
+            // use MDW.
+            //JAVA:    handlerDir.setPreventDoubleWrite(false);
+            handlerDir.PreventDoubleWrite = false;
+
+            // wrap sourceDirFactory to return a MockDirWrapper so we can simulate errors
+            ISourceDirectoryFactory @in = sourceDirFactory;
+            AtomicInt32 failures = new AtomicInt32(AtLeast(10));
+
+            // wrap sourceDirFactory to return a MockDirWrapper so we can simulate errors
+            sourceDirFactory = new SourceDirectoryFactoryAnonymousInnerClass(this, @in, failures);
+            handler = new IndexReplicationHandler(handlerDir, () =>
+            {
+                if (Random().NextDouble() < 0.2 && failures.Get() > 0)
+                    throw new Exception("random exception from callback");
+                return null;
+            });
+            client = new ReplicationClientAnonymousInnerClass(this, replicator, handler, sourceDirFactory, failures);
+            client.StartUpdateThread(10, "index");
+
+            Directory baseHandlerDir = handlerDir.Delegate;
+            int numRevisions = AtLeast(20);
+            for (int i = 2; i < numRevisions; i++)
+            {
+                replicator.Publish(CreateRevision(i));
+                AssertHandlerRevision(i, baseHandlerDir);
+            }
+
+            // disable errors -- maybe randomness didn't exhaust all allowed failures,
+            // and we don't want e.g. CheckIndex to hit false errors. 
+            handlerDir.MaxSizeInBytes = 0;
+            handlerDir.RandomIOExceptionRate=0.0;
+            handlerDir.RandomIOExceptionRateOnOpen=0.0;
+        }
+
+        private class SourceDirectoryFactoryAnonymousInnerClass : ISourceDirectoryFactory
+        {
+            private long clientMaxSize = 100, handlerMaxSize = 100;
+            private double clientExRate = 1.0, handlerExRate = 1.0;
+
+            private readonly IndexReplicationClientTest test;
+            private readonly ISourceDirectoryFactory @in;
+            private readonly AtomicInt32 failures;
+
+            public SourceDirectoryFactoryAnonymousInnerClass(IndexReplicationClientTest test, ISourceDirectoryFactory @in, AtomicInt32 failures)
+            {
+                this.test = test;
+                this.@in = @in;
+                this.failures = failures;
+            }
+
+            public void CleanupSession(string sessionId)
+            {
+                @in.CleanupSession(sessionId);
+            }
+
+            public Directory GetDirectory(string sessionId, string source)
+            {
+                Directory dir = @in.GetDirectory(sessionId, source);
+                if (Random().nextBoolean() && failures.Get() > 0)
+                { // client should fail, return wrapped dir
+                    MockDirectoryWrapper mdw = new MockDirectoryWrapper(Random(), dir);
+                    mdw.RandomIOExceptionRateOnOpen = clientExRate;
+                    mdw.MaxSizeInBytes = clientMaxSize;
+                    mdw.RandomIOExceptionRate = clientExRate;
+                    mdw.CheckIndexOnClose = false;
+                    clientMaxSize *= 2;
+                    clientExRate /= 2;
+                    return mdw;
+                }
+
+                if (failures.Get() > 0 && Random().nextBoolean())
+                { // handler should fail
+                    test.handlerDir.MaxSizeInBytes = handlerMaxSize;
+                    test.handlerDir.RandomIOExceptionRateOnOpen = handlerExRate;
+                    test.handlerDir.RandomIOExceptionRate = handlerExRate;
+                    handlerMaxSize *= 2;
+                    handlerExRate /= 2;
+                }
+                else
+                {
+                    // disable errors
+                    test.handlerDir.MaxSizeInBytes = 0;
+                    test.handlerDir.RandomIOExceptionRate = 0;
+                    test.handlerDir.RandomIOExceptionRateOnOpen = 0.0;
+                }
+                return dir;
+            }
+        }
+
+        private class ReplicationClientAnonymousInnerClass : ReplicationClient
+        {
+            private readonly IndexReplicationClientTest test;
+            private readonly AtomicInt32 failures;
+
+            public ReplicationClientAnonymousInnerClass(IndexReplicationClientTest test, IReplicator replicator, IReplicationHandler handler, ISourceDirectoryFactory factory, AtomicInt32 failures)
+                : base(replicator, handler, factory)
+            {
+                this.test = test;
+                this.failures = failures;
+            }
+
+            protected override void HandleUpdateException(Exception exception)
+            {
+                if (exception is IOException)
+                {
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("hit exception during update: " + exception);
+                    }
+
+                    try
+                    {
+                        // test that the index can be read and also some basic statistics
+                        DirectoryReader reader = DirectoryReader.Open(test.handlerDir.Delegate);
+                        try
+                        {
+                            int numDocs = reader.NumDocs;
+                            int version = int.Parse(reader.IndexCommit.UserData[VERSION_ID], NumberStyles.HexNumber);
+                            assertEquals(numDocs, version);
+                        }
+                        finally
+                        {
+                            reader.Dispose();
+                        }
+                        // verify index consistency
+                        TestUtil.CheckIndex(test.handlerDir.Delegate);
+                    }
+                    //TODO: Java had this, but considering what it does do we need it?
+                    //JAVA: catch (IOException e)
+                    //JAVA: {
+                    //JAVA:     // exceptions here are bad, don't ignore them
+                    //JAVA:     throw new RuntimeException(e);
+                    //JAVA: }
+                    finally
+                    {
+                        // count-down number of failures
+                        failures.DecrementAndGet();
+                        Debug.Assert(failures.Get() >= 0, "handler failed too many times: " + failures.Get());
+                        if (VERBOSE)
+                        {
+                            if (failures.Get() == 0)
+                            {
+                                Console.WriteLine("no more failures expected");
+                            }
+                            else
+                            {
+                                Console.WriteLine("num failures left: " + failures.Get());
+                            }
+                        }
+                    }
+                } else {
+                    //JAVA:          if (t instanceof RuntimeException) throw (RuntimeException) t;
+                    //JAVA:          throw new RuntimeException(t);
+                    throw exception;
+                }
+            }
+        }
+      
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/IndexRevisionTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/IndexRevisionTest.cs b/src/Lucene.Net.Tests.Replicator/IndexRevisionTest.cs
new file mode 100644
index 0000000..de4dbb4
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/IndexRevisionTest.cs
@@ -0,0 +1,177 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Linq;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Replicator;
+using Lucene.Net.Store;
+using Lucene.Net.Util;
+using NUnit.Framework;
+
+namespace Lucene.Net.Tests.Replicator
+{
+    /*
+	 * 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.
+	 */
+
+    public class IndexRevisionTest : ReplicatorTestCase
+    {
+        [Test]
+        public void TestNoSnapshotDeletionPolicy()
+        {
+            Directory dir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new KeepOnlyLastCommitDeletionPolicy();
+            IndexWriter writer = new IndexWriter(dir, conf);
+            try
+            {
+                assertNotNull(new IndexRevision(writer));
+                fail("should have failed when IndexDeletionPolicy is not Snapshot");
+            }
+            catch (ArgumentException)
+            {
+                // expected
+            }
+            finally
+            {
+                IOUtils.Dispose(writer, dir);
+            }
+        }
+
+        [Test]
+        public void TestNoCommit()
+        {
+            Directory dir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter writer = new IndexWriter(dir, conf);
+            try
+            {
+                assertNotNull(new IndexRevision(writer));
+                fail("should have failed when there are no commits to snapshot");
+            }
+            catch (InvalidOperationException)
+            {
+                // expected
+            }
+            finally
+            {
+                IOUtils.Dispose(writer, dir);
+            }
+        }
+
+        [Test]
+        public void TestRevisionRelease()
+        {
+            Directory dir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter writer = new IndexWriter(dir, conf);
+            try
+            {
+                writer.AddDocument(new Document());
+                writer.Commit();
+                IRevision rev1 = new IndexRevision(writer);
+                // releasing that revision should not delete the files
+                rev1.Release();
+                assertTrue(SlowFileExists(dir, IndexFileNames.SEGMENTS + "_1"));
+
+                rev1 = new IndexRevision(writer); // create revision again, so the files are snapshotted
+                writer.AddDocument(new Document());
+                writer.Commit();
+                assertNotNull(new IndexRevision(writer));
+                rev1.Release(); // this release should trigger the delete of segments_1
+                assertFalse(SlowFileExists(dir, IndexFileNames.SEGMENTS + "_1"));
+            }
+            finally
+            {
+                IOUtils.Dispose(writer, dir);
+            }
+        }
+
+        [Test]
+        public void TestSegmentsFileLast()
+        {
+            Directory dir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter writer = new IndexWriter(dir, conf);
+            try
+            {
+                writer.AddDocument(new Document());
+                writer.Commit();
+                IRevision rev = new IndexRevision(writer);
+                var sourceFiles = rev.SourceFiles;
+                assertEquals(1, sourceFiles.Count);
+                var files = sourceFiles.Values.First();
+                string lastFile = files.Last().FileName;
+                assertTrue(lastFile.StartsWith(IndexFileNames.SEGMENTS, StringComparison.Ordinal) && !lastFile.Equals(IndexFileNames.SEGMENTS_GEN));
+            }
+            finally
+            {
+                IOUtils.Dispose(writer, dir);
+            }
+        }
+
+        [Test]
+        public void TestOpen()
+        {
+            Directory dir = NewDirectory();
+            IndexWriterConfig conf = new IndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            IndexWriter writer = new IndexWriter(dir, conf);
+            try
+            {
+                writer.AddDocument(new Document());
+                writer.Commit();
+                IRevision rev = new IndexRevision(writer);
+                var sourceFiles = rev.SourceFiles;
+                string source = sourceFiles.Keys.First();
+                foreach (RevisionFile file in sourceFiles.Values.First())
+                {
+                    IndexInput src = dir.OpenInput(file.FileName, IOContext.READ_ONCE);
+                    System.IO.Stream @in = rev.Open(source, file.FileName);
+                    assertEquals(src.Length, @in.Length);
+                    byte[] srcBytes = new byte[(int) src.Length];
+                    byte[] inBytes = new byte[(int) src.Length];
+                    int offset = 0;
+                    if (Random().nextBoolean())
+                    {
+                        int skip = Random().Next(10);
+                        if (skip >= src.Length)
+                        {
+                            skip = 0;
+                        }
+                        //JAVA: in.skip(skip);
+                        byte[] skips = new byte[skip];
+                        @in.Read(skips, 0, skip);
+                        src.Seek(skip);
+                        offset = skip;
+                    }
+                    src.ReadBytes(srcBytes, offset, srcBytes.Length - offset);
+                    @in.Read(inBytes, offset, inBytes.Length - offset);
+                    assertArrayEquals(srcBytes, inBytes);
+                    IOUtils.Dispose(src, @in);
+                }
+            }
+            finally
+            {
+                IOUtils.Dispose(writer, dir);
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/LocalReplicatorTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/LocalReplicatorTest.cs b/src/Lucene.Net.Tests.Replicator/LocalReplicatorTest.cs
new file mode 100644
index 0000000..9946457
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/LocalReplicatorTest.cs
@@ -0,0 +1,225 @@
+//STATUS: DRAFT - 4.8.0
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Replicator;
+using Lucene.Net.Support;
+using Lucene.Net.Support.C5;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using Directory = Lucene.Net.Store.Directory;
+
+namespace Lucene.Net.Tests.Replicator
+{
+    /*
+	 * 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.
+	 */
+
+    public class LocalReplicatorTest : ReplicatorTestCase
+    {
+        private const string VERSION_ID = "version";
+
+        private LocalReplicator replicator;
+        private Directory sourceDirectory;
+        private IndexWriter sourceWriter;
+
+        public override void SetUp()
+        {
+            base.SetUp();
+
+            sourceDirectory = NewDirectory();
+            IndexWriterConfig conf = NewIndexWriterConfig(TEST_VERSION_CURRENT, null);
+            conf.IndexDeletionPolicy = new SnapshotDeletionPolicy(conf.IndexDeletionPolicy);
+            sourceWriter = new IndexWriter(sourceDirectory, conf);
+            replicator = new LocalReplicator();
+        }
+
+        public override void TearDown()
+        {
+            IOUtils.Dispose(replicator, sourceWriter, sourceDirectory);
+            base.TearDown();
+        }
+
+        private IRevision CreateRevision(int id)
+        {
+            sourceWriter.AddDocument(new Document());
+            //JAVA: sourceWriter.setCommitData(new HashMap<String, String>() {{
+            //JAVA:     put(VERSION_ID, Integer.toString(id, 16));
+            //JAVA: }
+            sourceWriter.SetCommitData(new Dictionary<string, string> {
+                { VERSION_ID, id.ToString() }
+            });
+            sourceWriter.Commit();
+            return new IndexRevision(sourceWriter);
+        }
+
+        [Test]
+        public void TestCheckForUpdateNoRevisions()
+        {
+            assertNull(replicator.CheckForUpdate(null));
+        }
+        
+        [Test]
+        public void TestObtainFileAlreadyClosed()
+        {
+            replicator.Publish(CreateRevision(1));
+            SessionToken res = replicator.CheckForUpdate(null);
+            assertNotNull(res);
+            assertEquals(1, res.SourceFiles.Count);
+            System.Collections.Generic.KeyValuePair<string, System.Collections.Generic.IList<RevisionFile>> entry = res.SourceFiles.First();
+            replicator.Dispose();
+            try
+            {
+                replicator.ObtainFile(res.Id, entry.Key, entry.Value.First().FileName);
+                fail("should have failed on AlreadyClosedException");
+            }
+            catch (ObjectDisposedException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public void TestPublishAlreadyClosed()
+        {
+            replicator.Dispose();
+            try
+            {
+                replicator.Publish(CreateRevision(2));
+                fail("should have failed on AlreadyClosedException");
+            }
+            catch (ObjectDisposedException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public void TestUpdateAlreadyClosed()
+        {
+            replicator.Dispose();
+            try
+            {
+                replicator.CheckForUpdate(null);
+                fail("should have failed on AlreadyClosedException");
+            }
+            catch (ObjectDisposedException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public void TestPublishSameRevision()
+        {
+            IRevision rev = CreateRevision(1);
+            replicator.Publish(rev);
+            SessionToken res = replicator.CheckForUpdate(null);
+            assertNotNull(res);
+            assertEquals(rev.Version, res.Version);
+            replicator.Release(res.Id);
+            replicator.Publish(new IndexRevision(sourceWriter));
+            res = replicator.CheckForUpdate(res.Version);
+            assertNull(res);
+
+            // now make sure that publishing same revision doesn't leave revisions
+            // "locked", i.e. that replicator releases revisions even when they are not
+            // kept
+            replicator.Publish(CreateRevision(2));
+            assertEquals(1, DirectoryReader.ListCommits(sourceDirectory).size());
+        }
+
+        [Test]
+        public void TestPublishOlderRev()
+        {
+            replicator.Publish(CreateRevision(1));
+            IRevision old = new IndexRevision(sourceWriter);
+            replicator.Publish(CreateRevision(2));
+            try
+            {
+                replicator.Publish(old);
+                fail("should have failed to publish an older revision");
+            }
+            catch (System.ArgumentException)
+            {
+                // expected
+            }
+            assertEquals(1, DirectoryReader.ListCommits(sourceDirectory).size());
+        }
+
+        [Test]
+        public void TestObtainMissingFile()
+        {
+            replicator.Publish(CreateRevision(1));
+            SessionToken res = replicator.CheckForUpdate(null);
+            try
+            {
+                replicator.ObtainFile(res.Id, res.SourceFiles.Keys.First(), "madeUpFile");
+                fail("should have failed obtaining an unrecognized file");
+            }
+            //JAVA: } catch (FileNotFoundException | NoSuchFileException e) { -> Could not find a "NoSuchFileException" ?NoSuchItemException
+            catch (Exception e) when (e is FileNotFoundException)//|| e is NoSuchItemException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public void TestSessionExpiration()
+        {
+            replicator.Publish(CreateRevision(1));
+            SessionToken session = replicator.CheckForUpdate(null);
+            replicator.ExpirationThreshold = 5; // expire quickly
+            Thread.Sleep(50); // sufficient for expiration
+            try
+            {
+                replicator.ObtainFile(session.Id, session.SourceFiles.Keys.First(), session.SourceFiles.Values.First().First().FileName);
+                fail("should have failed to obtain a file for an expired session");
+            }
+            catch (SessionExpiredException)
+            {
+                // expected
+            }
+        }
+
+        [Test]
+        public void TestUpdateToLatest()
+        {
+            replicator.Publish(CreateRevision(1));
+            IRevision rev = CreateRevision(2);
+            replicator.Publish(rev);
+            SessionToken res = replicator.CheckForUpdate(null);
+            assertNotNull(res);
+            assertEquals(0, rev.CompareTo(res.Version));
+        }
+
+        [Test]
+        public void TestRevisionRelease()
+        {
+            replicator.Publish(CreateRevision(1));
+            assertTrue(SlowFileExists(sourceDirectory, IndexFileNames.SEGMENTS + "_1"));
+            replicator.Publish(CreateRevision(2));
+            // now the files of revision 1 can be deleted
+            assertTrue(SlowFileExists(sourceDirectory, IndexFileNames.SEGMENTS + "_2"));
+            assertFalse("segments_1 should not be found in index directory after revision is released", SlowFileExists(sourceDirectory, IndexFileNames.SEGMENTS + "_1"));
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/6da4dd20/src/Lucene.Net.Tests.Replicator/Lucene.Net.Tests.Replicator.csproj
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Replicator/Lucene.Net.Tests.Replicator.csproj b/src/Lucene.Net.Tests.Replicator/Lucene.Net.Tests.Replicator.csproj
new file mode 100644
index 0000000..60adc6e
--- /dev/null
+++ b/src/Lucene.Net.Tests.Replicator/Lucene.Net.Tests.Replicator.csproj
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{418E9D8E-2369-4B52-8D2F-5A987213999B}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Lucene.Net.Tests.Replicator</RootNamespace>
+    <AssemblyName>Lucene.Net.Tests.Replicator</AssemblyName>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Hosting.1.0.3\lib\net451\Microsoft.AspNetCore.Hosting.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Hosting.Abstractions.1.0.3\lib\net451\Microsoft.AspNetCore.Hosting.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Hosting.Server.Abstractions.1.0.3\lib\net451\Microsoft.AspNetCore.Hosting.Server.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.Http, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Http.1.0.3\lib\net451\Microsoft.AspNetCore.Http.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.Http.Abstractions, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Http.Abstractions.1.0.3\lib\net451\Microsoft.AspNetCore.Http.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.Http.Extensions, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Http.Extensions.1.0.3\lib\net451\Microsoft.AspNetCore.Http.Extensions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.Http.Features, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.Http.Features.1.0.3\lib\net451\Microsoft.AspNetCore.Http.Features.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.TestHost, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.TestHost.1.0.3\lib\net451\Microsoft.AspNetCore.TestHost.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.AspNetCore.WebUtilities, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.AspNetCore.WebUtilities.1.0.3\lib\net451\Microsoft.AspNetCore.WebUtilities.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Configuration.1.0.2\lib\netstandard1.1\Microsoft.Extensions.Configuration.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Configuration.Abstractions.1.0.2\lib\netstandard1.0\Microsoft.Extensions.Configuration.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Configuration.EnvironmentVariables.1.0.2\lib\net451\Microsoft.Extensions.Configuration.EnvironmentVariables.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.DependencyInjection.1.0.2\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.2\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.FileProviders.Abstractions, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.FileProviders.Abstractions.1.0.1\lib\netstandard1.0\Microsoft.Extensions.FileProviders.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.FileProviders.Physical, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.FileProviders.Physical.1.0.1\lib\net451\Microsoft.Extensions.FileProviders.Physical.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.FileSystemGlobbing, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.FileSystemGlobbing.1.0.1\lib\net451\Microsoft.Extensions.FileSystemGlobbing.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Logging.1.0.2\lib\netstandard1.1\Microsoft.Extensions.Logging.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Logging.Abstractions.1.0.2\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.ObjectPool, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.ObjectPool.1.0.1\lib\net451\Microsoft.Extensions.ObjectPool.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Options, Version=1.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Options.1.0.2\lib\netstandard1.0\Microsoft.Extensions.Options.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.PlatformAbstractions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.PlatformAbstractions.1.0.0\lib\net451\Microsoft.Extensions.PlatformAbstractions.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Primitives, Version=1.0.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Extensions.Primitives.1.0.1\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Microsoft.Net.Http.Headers, Version=1.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Microsoft.Net.Http.Headers.1.0.3\lib\netstandard1.1\Microsoft.Net.Http.Headers.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Buffers.4.0.0\lib\netstandard1.1\System.Buffers.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Collections.Immutable, Version=1.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Collections.Immutable.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.ComponentModel.Composition" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Diagnostics.DiagnosticSource.4.0.0\lib\portable-net45+win8+wpa81\System.Diagnostics.DiagnosticSource.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reflection.Metadata, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Runtime.InteropServices.RuntimeInformation.4.0.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=4.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\System.Text.Encodings.Web.4.0.1\lib\netstandard1.0\System.Text.Encodings.Web.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="..\CommonAssemblyInfo.cs">
+      <Link>Properties\CommonAssemblyInfo.cs</Link>
+    </Compile>
+    <Compile Include="Http\HttpReplicatorTest.cs" />
+    <Compile Include="Http\ReplicationServlet.cs" />
+    <Compile Include="IndexAndTaxonomyReplicationClientTest.cs" />
+    <Compile Include="IndexAndTaxonomyRevisionTest.cs" />
+    <Compile Include="IndexReplicationClientTest.cs" />
+    <Compile Include="IndexRevisionTest.cs" />
+    <Compile Include="LocalReplicatorTest.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ReplicatorTestCase.cs" />
+    <Compile Include="SessionTokenTest.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Lucene.Net.Facet\Lucene.Net.Facet.csproj">
+      <Project>{48F7884A-9454-4E88-8413-9D35992CB440}</Project>
+      <Name>Lucene.Net.Facet</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.Replicator.AspNetCore\Lucene.Net.Replicator.AspNetCore.csproj">
+      <Project>{763ccb5a-e397-456a-af47-7c6e228b1852}</Project>
+      <Name>Lucene.Net.Replicator.AspNetCore</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.Replicator\Lucene.Net.Replicator.csproj">
+      <Project>{1F70D2DB-C1B3-4F78-9598-3E04E0C7EB06}</Project>
+      <Name>Lucene.Net.Replicator</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.TestFramework\Lucene.Net.TestFramework.csproj">
+      <Project>{B2C0D749-CE34-4F62-A15E-00CB2FF5DDB3}</Project>
+      <Name>Lucene.Net.TestFramework</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net\Lucene.Net.csproj">
+      <Project>{5D4AD9BE-1FFB-41AB-9943-25737971BF57}</Project>
+      <Name>Lucene.Net</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file