You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ch...@apache.org on 2015/05/21 17:26:46 UTC

svn commit: r1680903 [2/2] - in /jackrabbit/oak/trunk/oak-lucene/src: main/java/org/apache/jackrabbit/oak/plugins/index/lucene/ test/java/org/apache/jackrabbit/oak/plugins/index/lucene/

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java?rev=1680903&r1=1680902&r2=1680903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/IndexCopierTest.java Thu May 21 15:26:46 2015
@@ -20,25 +20,35 @@
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import javax.management.openmbean.TabularData;
 
+import com.google.common.base.StandardSystemProperty;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.ForwardingListeningExecutorService;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
+import org.apache.commons.io.FileUtils;
 import org.apache.jackrabbit.oak.commons.IOUtils;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -47,19 +57,21 @@ import org.apache.lucene.store.IOContext
 import org.apache.lucene.store.IndexInput;
 import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.store.RAMDirectory;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
 import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Sets.newHashSet;
 import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
 import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_COUNT;
 import static org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent.INITIAL_CONTENT;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 public class IndexCopierTest {
     private Random rnd = new Random();
@@ -79,7 +91,7 @@ public class IndexCopierTest {
         IndexCopier c1 = new RAMIndexCopier(baseDir, sameThreadExecutor(), getWorkDir());
 
         Directory remote = new RAMDirectory();
-        Directory wrapped = c1.wrap("/foo" , defn, remote);
+        Directory wrapped = c1.wrapForRead("/foo", defn, remote);
 
         byte[] t1 = writeFile(remote , "t1");
         byte[] t2 = writeFile(remote , "t2");
@@ -104,7 +116,7 @@ public class IndexCopierTest {
         IndexCopier c1 = new IndexCopier(sameThreadExecutor(), getWorkDir());
 
         Directory remote = new RAMDirectory();
-        Directory wrapped = c1.wrap("/foo" , defn, remote);
+        Directory wrapped = c1.wrapForRead("/foo", defn, remote);
 
         byte[] t1 = writeFile(remote, "t1");
         byte[] t2 = writeFile(remote , "t2");
@@ -128,16 +140,16 @@ public class IndexCopierTest {
         assertEquals(1, td.size());
     }
 
-    @Ignore("OAK-2722") //FIXME test fails on windows
     @Test
     public void deleteOldPostReindex() throws Exception{
+        assumeNotWindows();
         IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
         IndexCopier c1 = new IndexCopier(sameThreadExecutor(), getWorkDir());
 
         Directory remote = new CloseSafeDir();
-        Directory w1 = c1.wrap("/foo" , defn, remote);
+        Directory w1 = c1.wrapForRead("/foo", defn, remote);
 
-        byte[] t1 = writeFile(remote , "t1");
+        byte[] t1 = writeFile(remote, "t1");
         byte[] t2 = writeFile(remote , "t2");
 
         readAndAssert(w1, "t1", t1);
@@ -154,7 +166,7 @@ public class IndexCopierTest {
         //Close old version
         w1.close();
         //Get a new one with updated reindexCount
-        Directory w2 = c1.wrap("/foo" , defn, remote);
+        Directory w2 = c1.wrapForRead("/foo", defn, remote);
 
         readAndAssert(w2, "t1", t1);
 
@@ -174,7 +186,7 @@ public class IndexCopierTest {
         IndexCopier c1 = new RAMIndexCopier(baseDir, executor, getWorkDir());
 
         TestRAMDirectory remote = new TestRAMDirectory();
-        Directory wrapped = c1.wrap("/foo", defn, remote);
+        Directory wrapped = c1.wrapForRead("/foo", defn, remote);
 
         byte[] t1 = writeFile(remote , "t1");
 
@@ -237,7 +249,7 @@ public class IndexCopierTest {
                 super.copy(to, src, dest, context);
             }
         };
-        Directory wrapped = c1.wrap("/foo", defn, remote);
+        Directory wrapped = c1.wrapForRead("/foo", defn, remote);
 
         byte[] t1 = writeFile(remote , "t1");
 
@@ -281,7 +293,7 @@ public class IndexCopierTest {
         IndexCopier c1 = new RAMIndexCopier(baseDir, sameThreadExecutor(), getWorkDir());
 
         TestRAMDirectory remote = new TestRAMDirectory();
-        Directory wrapped = c1.wrap("/foo" , defn, remote);
+        Directory wrapped = c1.wrapForRead("/foo", defn, remote);
 
         byte[] t1 = writeFile(remote, "t1");
 
@@ -290,7 +302,7 @@ public class IndexCopierTest {
         assertEquals(1, remote.openedFiles.size());
 
         //2. Reuse the testDir and read again
-        Directory wrapped2 = c1.wrap("/foo", defn, remote);
+        Directory wrapped2 = c1.wrapForRead("/foo", defn, remote);
         remote.reset();
 
         //3. Now read should be served from local
@@ -298,7 +310,7 @@ public class IndexCopierTest {
         assertEquals(0, remote.openedFiles.size());
 
         //Now check if local file gets corrupted then read from remote
-        Directory wrapped3 = c1.wrap("/foo" , defn, remote);
+        Directory wrapped3 = c1.wrapForRead("/foo", defn, remote);
         remote.reset();
 
         //4. Corrupt the local copy
@@ -323,7 +335,7 @@ public class IndexCopierTest {
         };
 
         String fileName = "failed.txt";
-        Directory wrapped = c1.wrap("/foo" , defn, remote);
+        Directory wrapped = c1.wrapForRead("/foo", defn, remote);
 
         byte[] t1 = writeFile(remote , fileName);
 
@@ -351,10 +363,10 @@ public class IndexCopierTest {
 
         Directory r1 = new RAMDirectory();
 
-        byte[] t1 = writeFile(r1 , "t1");
+        byte[] t1 = writeFile(r1, "t1");
         byte[] t2 = writeFile(r1 , "t2");
 
-        Directory w1 = c1.wrap("/foo" , defn, r1);
+        Directory w1 = c1.wrapForRead("/foo", defn, r1);
         readAndAssert(w1, "t1", t1);
         readAndAssert(w1, "t2", t2);
 
@@ -366,7 +378,7 @@ public class IndexCopierTest {
         copy(r1, r2);
         r2.deleteFile("t1");
 
-        Directory w2 = c1.wrap("/foo" , defn, r2);
+        Directory w2 = c1.wrapForRead("/foo", defn, r2);
 
         //Close would trigger removal of file which are not present in remote
         w2.close();
@@ -397,7 +409,7 @@ public class IndexCopierTest {
         byte[] t1 = writeFile(r1, "t1");
         byte[] t2 = writeFile(r1 , "t2");
 
-        Directory w1 = c1.wrap("/foo" , defn, r1);
+        Directory w1 = c1.wrapForRead("/foo", defn, r1);
         readAndAssert(w1, "t1", t1);
         readAndAssert(w1, "t2", t2);
 
@@ -409,7 +421,7 @@ public class IndexCopierTest {
         copy(r1, r2);
         r2.deleteFile("t1");
 
-        Directory w2 = c1.wrap("/foo" , defn, r2);
+        Directory w2 = c1.wrapForRead("/foo", defn, r2);
 
         //Close would trigger removal of file which are not present in remote
         testFiles.add("t1");
@@ -422,14 +434,14 @@ public class IndexCopierTest {
         assertEquals(IOUtils.humanReadableByteCount(t1.length), c1.getGarbageSize());
         assertEquals(1, c1.getGarbageDetails().length);
 
-        Directory w3 = c1.wrap("/foo" , defn, r2);
+        Directory w3 = c1.wrapForRead("/foo", defn, r2);
         w3.close();
         assertEquals(2, testFile.getDeleteAttemptCount());
 
         //Now let the file to be deleted
         testFiles.clear();
 
-        Directory w4 = c1.wrap("/foo" , defn, r2);
+        Directory w4 = c1.wrapForRead("/foo", defn, r2);
         w4.close();
 
         //No pending deletes left
@@ -447,14 +459,14 @@ public class IndexCopierTest {
         Directory remote1 = new RAMDirectory();
         byte[] t1 = writeFile(remote1, "t1");
 
-        Directory local1 = copier.wrap("/foo", defn, remote1);
+        Directory local1 = copier.wrapForRead("/foo", defn, remote1);
         readAndAssert(local1, "t1", t1);
 
         //While local1 is open , open another local2 and read t2
         Directory remote2 = new RAMDirectory();
         byte[] t2 = writeFile(remote2, "t2");
 
-        Directory local2 = copier.wrap("/foo", defn, remote2);
+        Directory local2 = copier.wrapForRead("/foo", defn, remote2);
         readAndAssert(local2, "t2", t2);
 
         //Close local1
@@ -464,6 +476,345 @@ public class IndexCopierTest {
         readAndAssert(local2, "t2", t2);
     }
 
+    @Test
+    public void wrapForWriteWithoutIndexPath() throws Exception{
+        assumeNotWindows();
+        Directory remote = new CloseSafeDir();
+
+        IndexCopier copier = new IndexCopier(sameThreadExecutor(), getWorkDir());
+
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        Directory dir = copier.wrapForWrite(defn, remote, false);
+
+        byte[] t1 = writeFile(dir, "t1");
+
+        dir.close();
+
+        readAndAssert(remote, "t1", t1);
+        //Work dir must be empty post close
+        assertArrayEquals(FileUtils.EMPTY_FILE_ARRAY, copier.getIndexWorkDir().listFiles());
+    }
+
+    @Test
+    public void wrapForWriteWithIndexPath() throws Exception{
+        assumeNotWindows();
+        Directory remote = new CloseSafeDir();
+
+        IndexCopier copier = new IndexCopier(sameThreadExecutor(), getWorkDir());
+
+        builder.setProperty(LuceneIndexConstants.INDEX_PATH, "foo");
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        Directory dir = copier.wrapForWrite(defn, remote, false);
+
+        byte[] t1 = writeFile(dir, "t1");
+
+        dir.close();
+
+        readAndAssert(remote, "t1", t1);
+        //Work dir must be empty post close
+        List<File> files = new ArrayList<File>(FileUtils.listFiles(copier.getIndexRootDir(), null, true));
+        assertEquals(1, files.size());
+        assertEquals("t1", files.get(0).getName());
+    }
+
+    @Test
+    public void copyOnWriteBasics() throws Exception{
+        Directory baseDir = new CloseSafeDir();
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        IndexCopier copier = new RAMIndexCopier(baseDir, sameThreadExecutor(), getWorkDir());
+
+        Directory remote = new RAMDirectory();
+        byte[] t1 = writeFile(remote, "t1");
+
+        //State of remote directory should set before wrapping as later
+        //additions would not be picked up given COW assume remote directory
+        //to be read only
+        Directory local = copier.wrapForWrite(defn, remote, false);
+
+        assertEquals(newHashSet("t1"), newHashSet(local.listAll()));
+        assertEquals(t1.length, local.fileLength("t1"));
+
+        byte[] t2 = writeFile(local, "t2");
+        assertEquals(newHashSet("t1", "t2"), newHashSet(local.listAll()));
+        assertEquals(t2.length, local.fileLength("t2"));
+
+        assertTrue(local.fileExists("t1"));
+        assertTrue(local.fileExists("t2"));
+
+        assertTrue("t2 should be copied to remote", remote.fileExists("t2"));
+
+        readAndAssert(local, "t1", t1);
+        readAndAssert(local, "t2", t2);
+
+        local.deleteFile("t1");
+        assertEquals(newHashSet("t2"), newHashSet(local.listAll()));
+
+        local.deleteFile("t2");
+        assertEquals(newHashSet(), newHashSet(local.listAll()));
+
+
+        try {
+            local.fileLength("nonExistentFile");
+            fail();
+        } catch (FileNotFoundException ignore) {
+
+        }
+
+        try {
+            local.openInput("nonExistentFile", IOContext.DEFAULT);
+            fail();
+        } catch (FileNotFoundException ignore) {
+
+        }
+
+        local.close();
+        assertFalse(baseDir.fileExists("t2"));
+    }
+
+    /**
+     * Checks for the case where if the file exist local before writer starts
+     * then those files do not get deleted even if deleted by writer via
+     * indexing process from 'baseDir' as they might be in use by existing open
+     * indexes
+     */
+    @Test
+    public void cowExistingLocalFileNotDeleted() throws Exception{
+        Directory baseDir = new CloseSafeDir();
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        IndexCopier copier = new RAMIndexCopier(baseDir, sameThreadExecutor(), getWorkDir());
+
+        Directory remote = new CloseSafeDir();
+        byte[] t1 = writeFile(remote, "t1");
+        byte[] t2 = writeFile(remote, "t2");
+        Directory local = copier.wrapForWrite(defn, remote, false);
+        assertEquals(newHashSet("t1", "t2"), newHashSet(local.listAll()));
+
+        byte[] t3 = writeFile(local, "t3");
+
+        //Now pull in the file t1 via CopyOnRead in baseDir
+        Directory localForRead = copier.wrapForRead("/foo", defn, remote);
+        readAndAssert(localForRead, "t1", t1);
+
+        //File which was copied from remote should not be deleted from baseDir
+        //upon delete from local
+        assertTrue(baseDir.fileExists("t1"));
+        local.deleteFile("t1");
+        assertFalse("t1 should be deleted from remote", remote.fileExists("t1"));
+        assertFalse("t1 should be deleted from 'local' view also", local.fileExists("t1"));
+        assertTrue("t1 should not be deleted from baseDir", baseDir.fileExists("t1"));
+
+        //File which was created only via local SHOULD get removed from
+        //baseDir only upon close
+        assertTrue(baseDir.fileExists("t3"));
+        local.deleteFile("t3");
+        assertFalse("t1 should be deleted from remote", local.fileExists("t3"));
+        assertTrue("t1 should NOT be deleted from remote", baseDir.fileExists("t3"));
+
+        local.close();
+        assertFalse("t3 should also be deleted from local", baseDir.fileExists("t3"));
+    }
+
+    @Test
+    public void cowReadDoneFromLocalIfFileExist() throws Exception{
+        final Set<String> readLocal = newHashSet();
+        Directory baseDir = new CloseSafeDir(){
+            @Override
+            public IndexInput openInput(String name, IOContext context) throws IOException {
+                readLocal.add(name);
+                return super.openInput(name, context);
+            }
+        };
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        IndexCopier copier = new RAMIndexCopier(baseDir, sameThreadExecutor(), getWorkDir());
+
+        final Set<String> readRemotes = newHashSet();
+        Directory remote = new RAMDirectory() {
+            @Override
+            public IndexInput openInput(String name, IOContext context) throws IOException {
+                readRemotes.add(name);
+                return super.openInput(name, context);
+            }
+        };
+        byte[] t1 = writeFile(remote, "t1");
+        Directory local = copier.wrapForWrite(defn, remote, false);
+
+        //Read should be served from remote
+        readRemotes.clear();readLocal.clear();
+        readAndAssert(local, "t1", t1);
+        assertEquals(newHashSet("t1"), readRemotes);
+        assertEquals(newHashSet(), readLocal);
+
+        //Now pull in the file t1 via CopyOnRead in baseDir
+        Directory localForRead = copier.wrapForRead("/foo", defn, remote);
+        readAndAssert(localForRead, "t1", t1);
+
+        //Read should be served from local
+        readRemotes.clear();readLocal.clear();
+        readAndAssert(local, "t1", t1);
+        assertEquals(newHashSet(), readRemotes);
+        assertEquals(newHashSet("t1"), readLocal);
+
+        local.close();
+    }
+
+    @Test
+    public void cowCopyDoneOnClose() throws Exception{
+        final CollectingExecutor executor = new CollectingExecutor();
+        Directory baseDir = new CloseSafeDir();
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        IndexCopier copier = new RAMIndexCopier(baseDir, executor, getWorkDir());
+
+        Directory remote = new CloseSafeDir();
+
+        final Directory local = copier.wrapForWrite(defn, remote, false);
+        byte[] t1 = writeFile(local, "t1");
+
+        assertTrue(local.fileExists("t1"));
+        assertFalse("t1 should NOT be copied to remote", remote.fileExists("t1"));
+
+        //Execute all job
+        executor.executeAll();
+
+        assertTrue("t1 should now be copied to remote", remote.fileExists("t1"));
+
+        byte[] t2 = writeFile(local, "t2");
+        assertFalse("t2 should NOT be copied to remote", remote.fileExists("t2"));
+
+        final ExecutorService executorService = Executors.newFixedThreadPool(4);
+        final CountDownLatch copyLatch = new CountDownLatch(1);
+        Future<?> copyTasks = executorService.submit(new Callable<Object>() {
+            @Override
+            public Object call() throws Exception {
+                copyLatch.await();
+                //the executor to a proper one as it might happen that
+                //STOP task is added post CountingExecutor has executed. Then there
+                //would be none to process the STOP. Having a proper executor would
+                //handle that case
+                executor.setForwardingExecutor(executorService);
+                executor.executeAll();
+                return null;
+            }
+        });
+
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        Future<?> closeTasks = executorService.submit(new Callable<Object>() {
+            @Override
+            public Object call() throws Exception {
+                closeLatch.await();
+                local.close();
+                return null;
+            }
+        });
+
+        closeLatch.countDown();
+        assertFalse("t2 should NOT be copied to remote", remote.fileExists("t2"));
+
+        //Let copy to proceed
+        copyLatch.countDown();
+
+        //Now wait for close to finish
+        closeTasks.get();
+        assertTrue("t2 should now be copied to remote", remote.fileExists("t2"));
+
+        executorService.shutdown();
+    }
+
+    @Test
+    public void cowCopyDoneOnCloseExceptionHandling() throws Exception{
+        final CollectingExecutor executor = new CollectingExecutor();
+        Directory baseDir = new CloseSafeDir();
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        IndexCopier copier = new RAMIndexCopier(baseDir, executor, getWorkDir());
+
+        Directory remote = new CloseSafeDir();
+
+        final Directory local = copier.wrapForWrite(defn, remote, false);
+        byte[] t1 = writeFile(local, "t1");
+
+        assertTrue(local.fileExists("t1"));
+        assertFalse("t1 should NOT be copied to remote", remote.fileExists("t1"));
+
+        //Execute all job
+        executor.executeAll();
+
+        assertTrue("t1 should now be copied to remote", remote.fileExists("t1"));
+
+        byte[] t2 = writeFile(local, "t2");
+        assertFalse("t2 should NOT be copied to remote", remote.fileExists("t2"));
+
+        ExecutorService executorService = Executors.newFixedThreadPool(2);
+        final CountDownLatch copyLatch = new CountDownLatch(1);
+        Future<?> copyTasks = executorService.submit(new Callable<Object>() {
+            @Override
+            public Object call() throws Exception {
+                copyLatch.await();
+                executor.executeAll();
+                executor.enableImmediateExecution();
+                return null;
+            }
+        });
+
+        final CountDownLatch closeLatch = new CountDownLatch(1);
+        Future<?> closeTasks = executorService.submit(new Callable<Object>() {
+            @Override
+            public Object call() throws Exception {
+                closeLatch.await();
+                local.close();
+                return null;
+            }
+        });
+
+        closeLatch.countDown();
+        assertFalse("t2 should NOT be copied to remote", remote.fileExists("t2"));
+
+        //Let copy to proceed
+        copyLatch.countDown();
+
+        //Now wait for close to finish
+        closeTasks.get();
+        assertTrue("t2 should now be copied to remote", remote.fileExists("t2"));
+
+        executorService.shutdown();
+    }
+
+    @Test
+    public void cowFailureInCopy() throws Exception{
+        ExecutorService executorService = Executors.newFixedThreadPool(2);
+        Directory baseDir = new CloseSafeDir();
+        IndexDefinition defn = new IndexDefinition(root, builder.getNodeState());
+        IndexCopier copier = new RAMIndexCopier(baseDir, executorService, getWorkDir());
+
+        final Set<String> toFail = Sets.newHashSet();
+        Directory remote = new CloseSafeDir() {
+            @Override
+            public IndexOutput createOutput(String name, IOContext context) throws IOException {
+                if (toFail.contains(name)){
+                    throw new RuntimeException("Failing copy for "+name);
+                }
+                return super.createOutput(name, context);
+            }
+        };
+
+        final Directory local = copier.wrapForWrite(defn, remote, false);
+        toFail.add("t2");
+        byte[] t1 = writeFile(local, "t1");
+        byte[] t2 = writeFile(local, "t2");
+
+        try {
+            local.close();
+            fail();
+        } catch (IOException ignore){
+
+        }
+
+        executorService.shutdown();
+    }
+
+    @Test
+    public void cowIndexPathNotDefined() throws Exception{
+
+    }
+
     private byte[] writeFile(Directory dir, String name) throws IOException {
         byte[] data = randomBytes(rnd.nextInt(maxFileSize) + 1);
         IndexOutput o = dir.createOutput(name, IOContext.DEFAULT);
@@ -498,13 +849,18 @@ public class IndexCopierTest {
     private class RAMIndexCopier extends IndexCopier {
         final Directory baseDir;
 
-        public RAMIndexCopier(Directory baseDir, Executor executor, File indexRootDir) {
+        public RAMIndexCopier(Directory baseDir, Executor executor, File indexRootDir) throws IOException {
             super(executor, indexRootDir);
             this.baseDir = baseDir;
         }
 
         @Override
-        protected Directory createLocalDir(String indexPath, IndexDefinition definition) throws IOException {
+        protected Directory createLocalDirForIndexReader(String indexPath, IndexDefinition definition) throws IOException {
+            return baseDir;
+        }
+
+        @Override
+        protected Directory createLocalDirForIndexWriter(IndexDefinition definition) throws IOException {
             return baseDir;
         }
     }
@@ -531,18 +887,44 @@ public class IndexCopierTest {
     }
 
     private static class CollectingExecutor implements Executor {
-        final List<Runnable> commands = newArrayList();
+        final BlockingQueue<Runnable> commands = new LinkedBlockingQueue<Runnable>();
+        private boolean immediateExecution = false;
+        private volatile Executor forwardingExecutor;
 
         @Override
         public void execute(Runnable command) {
-            commands.add(command);
+            if (forwardingExecutor != null){
+                forwardingExecutor.execute(command);
+                return;
+            }
+            if (immediateExecution){
+                command.run();
+            } else {
+                commands.add(command);
+            }
         }
 
         void executeAll(){
-            for (Runnable c : commands) {
+            Runnable c;
+            while ((c = commands.poll()) != null){
                 c.run();
             }
         }
+
+        void enableImmediateExecution(){
+            immediateExecution = true;
+        }
+
+        void enableDelayedExecution(){
+            immediateExecution = false;
+        }
+
+        void setForwardingExecutor(Executor forwardingExecutor){
+            this.forwardingExecutor = forwardingExecutor;
+        }
     }
 
+    private static void assumeNotWindows() {
+        assumeTrue(!StandardSystemProperty.OS_NAME.value().toLowerCase().contains("windows"));
+    }
 }

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java?rev=1680903&r1=1680902&r2=1680903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java Thu May 21 15:26:46 2015
@@ -22,16 +22,20 @@ package org.apache.jackrabbit.oak.plugin
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
 import org.apache.jackrabbit.oak.spi.commit.BackgroundObserver;
 import org.apache.jackrabbit.oak.spi.commit.Observer;
 import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
+import org.apache.lucene.util.InfoStream;
 import org.apache.sling.testing.mock.osgi.MockOsgi;
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 public class LuceneIndexProviderServiceTest {
@@ -50,10 +54,16 @@ public class LuceneIndexProviderServiceT
 
         assertNotNull(context.getService(QueryIndexProvider.class));
         assertNotNull(context.getService(Observer.class));
+        assertNotNull(context.getService(IndexEditorProvider.class));
 
-        assertNotNull("CopyOnRead should be enabled by default",context.getService(CopyOnReadStatsMBean.class));
+        LuceneIndexEditorProvider editorProvider =
+                (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class);
+        assertNull(editorProvider.getIndexCopier());
+
+        assertNotNull("CopyOnRead should be enabled by default", context.getService(CopyOnReadStatsMBean.class));
 
         assertTrue(context.getService(Observer.class) instanceof BackgroundObserver);
+        assertEquals(InfoStream.NO_OUTPUT, InfoStream.getDefault());
 
         MockOsgi.deactivate(service);
     }
@@ -69,6 +79,31 @@ public class LuceneIndexProviderServiceT
         MockOsgi.deactivate(service);
     }
 
+    @Test
+    public void enableCopyOnWrite() throws Exception{
+        Map<String,Object> config = getDefaultConfig();
+        config.put("enableCopyOnWriteSupport", true);
+        MockOsgi.activate(service, context.bundleContext(), config);
+
+        LuceneIndexEditorProvider editorProvider =
+                (LuceneIndexEditorProvider) context.getService(IndexEditorProvider.class);
+
+        assertNotNull(editorProvider);
+        assertNotNull(editorProvider.getIndexCopier());
+
+        MockOsgi.deactivate(service);
+    }
+
+    @Test
+    public void debugLogging() throws Exception{
+        Map<String,Object> config = getDefaultConfig();
+        config.put("debug", true);
+        MockOsgi.activate(service, context.bundleContext(), config);
+
+        assertEquals(LoggingInfoStream.INSTANCE, InfoStream.getDefault());
+        MockOsgi.deactivate(service);
+    }
+
     private Map<String,Object> getDefaultConfig(){
         Map<String,Object> config = new HashMap<String, Object>();
         config.put("localIndexDir", folder.getRoot().getAbsolutePath());

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java?rev=1680903&r1=1680902&r2=1680903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java Thu May 21 15:26:46 2015
@@ -108,6 +108,7 @@ import org.junit.Test;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
+@SuppressWarnings("ConstantConditions")
 public class LuceneIndexTest {
 
     private static final EditorHook HOOK = new EditorHook(
@@ -341,7 +342,7 @@ public class LuceneIndexTest {
     }
 
     private void purgeDeletedDocs(NodeBuilder idx, IndexDefinition definition) throws IOException {
-        IndexWriter writer = new IndexWriter(newIndexDirectory(definition, idx), getIndexWriterConfig(definition));
+        IndexWriter writer = new IndexWriter(newIndexDirectory(definition, idx), getIndexWriterConfig(definition, true));
         writer.forceMergeDeletes();
         writer.close();
     }

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java?rev=1680903&r1=1680902&r2=1680903&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndexTest.java Thu May 21 15:26:46 2015
@@ -20,12 +20,15 @@
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
 import java.io.InputStream;
+import java.io.IOException;
 import java.text.ParseException;
 import java.util.Calendar;
 import java.util.Collections;
 import java.util.List;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import javax.annotation.Nonnull;
 import javax.jcr.PropertyType;
@@ -59,7 +62,10 @@ import org.apache.jackrabbit.oak.spi.com
 import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
 import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
 import org.apache.jackrabbit.util.ISO8601;
+import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 
 import static com.google.common.collect.ImmutableSet.of;
 import static java.util.Arrays.asList;
@@ -95,6 +101,11 @@ public class LucenePropertyIndexTest ext
      */
     static final int NUMBER_OF_NODES = LucenePropertyIndex.LUCENE_QUERY_BATCH_SIZE * 2;
 
+    private ExecutorService executorService = Executors.newFixedThreadPool(2);
+
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
     @Override
     protected void createTestIndexNode() throws Exception {
         setTraversalEnabled(false);
@@ -108,12 +119,25 @@ public class LucenePropertyIndexTest ext
                 .with(new OpenSecurityProvider())
                 .with((QueryIndexProvider) provider)
                 .with((Observer) provider)
-                .with(new LuceneIndexEditorProvider())
+                .with(new LuceneIndexEditorProvider(createIndexCopier()))
                 .with(new PropertyIndexEditorProvider())
                 .with(new NodeTypeIndexProvider())
                 .createContentRepository();
     }
 
+    private IndexCopier createIndexCopier() {
+        try {
+            return new IndexCopier(executorService, temporaryFolder.getRoot());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @After
+    public void shutdownExecutor(){
+        executorService.shutdown();
+    }
+
     @Test
     public void fulltextSearchWithCustomAnalyzer() throws Exception {
         Tree idx = createFulltextIndex(root.getTree("/"), "test");