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 ca...@apache.org on 2017/10/01 00:42:18 UTC

svn commit: r1810243 - in /jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene: LuceneIndexProviderServiceTest.java directory/BufferedOakDirectoryTest.java directory/OakStreamingIndexFileTest.java

Author: catholicon
Date: Sun Oct  1 00:42:18 2017
New Revision: 1810243

URL: http://svn.apache.org/viewvc?rev=1810243&view=rev
Log:
OAK-6269: Support non chunk storage in OakDirectory

Add test for OakStreamingIndexFile and a few tests in BufferedOakDirectoryTest

Added:
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/OakStreamingIndexFileTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexProviderServiceTest.java
    jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/BufferedOakDirectoryTest.java

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=1810243&r1=1810242&r2=1810243&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 Sun Oct  1 00:42:18 2017
@@ -47,6 +47,7 @@ import org.apache.jackrabbit.oak.plugins
 import org.apache.jackrabbit.oak.plugins.index.IndexPathService;
 import org.apache.jackrabbit.oak.plugins.index.fulltext.PreExtractedTextProvider;
 import org.apache.jackrabbit.oak.plugins.index.importer.IndexImporterProvider;
+import org.apache.jackrabbit.oak.plugins.index.lucene.directory.BufferedOakDirectory;
 import org.apache.jackrabbit.oak.plugins.index.lucene.score.ScorerProviderFactory;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.jackrabbit.oak.spi.blob.GarbageCollectableBlobStore;
@@ -63,7 +64,6 @@ import org.apache.sling.testing.mock.osg
 import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -316,6 +316,23 @@ public class LuceneIndexProviderServiceT
     }
 
 
+    @Test
+    public void singleBlobPerIndexFileConfig() throws Exception {
+        Map<String, Object> config = getDefaultConfig();
+        config.put("enableSingleBlobIndexFiles", "true");
+        MockOsgi.activate(service, context.bundleContext(), config);
+        assertTrue("Enabling property must reflect in BufferedOakDirectory state",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+        MockOsgi.deactivate(service, context.bundleContext());
+
+        config = getDefaultConfig();
+        config.put("enableSingleBlobIndexFiles", "false");
+        MockOsgi.activate(service, context.bundleContext(), config);
+        assertFalse("Enabling property must reflect in BufferedOakDirectory state",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+        MockOsgi.deactivate(service, context.bundleContext());
+
+    }
 
     private void reactivate() {
         MockOsgi.deactivate(service, context.bundleContext());

Modified: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/BufferedOakDirectoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/BufferedOakDirectoryTest.java?rev=1810243&r1=1810242&r2=1810243&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/BufferedOakDirectoryTest.java (original)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/BufferedOakDirectoryTest.java Sun Oct  1 00:42:18 2017
@@ -22,9 +22,12 @@ import java.util.Arrays;
 import java.util.Random;
 import java.util.Set;
 
+import ch.qos.logback.classic.Level;
 import com.google.common.collect.Sets;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
 import org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition;
 import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
@@ -37,6 +40,8 @@ import org.junit.Test;
 
 import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.INDEX_DATA_CHILD_NAME;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.BufferedOakDirectory.DELETE_THRESHOLD_UNTIL_REOPEN;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.BufferedOakDirectory.ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.BufferedOakDirectory.reReadCommandLineParam;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -143,6 +148,251 @@ public class BufferedOakDirectoryTest {
         dir.close();
     }
 
+    @Test
+    public void respectSettingConfigForSingleBlobWrite() {
+        boolean oldVal = BufferedOakDirectory.isEnableWritingSingleBlobIndexFile();
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        assertTrue("Flag not setting as set by configuration",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        assertFalse("Flag not setting as set by configuration",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(oldVal);
+    }
+
+    @Test
+    public void selectWriteStrategyBasedOnFlagAndMode() throws Exception {
+        boolean oldVal = BufferedOakDirectory.isEnableWritingSingleBlobIndexFile();
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            IndexOutput multiBlobIndexOutput = multiBlobDir.createOutput("foo", IOContext.DEFAULT);
+
+            multiBlobIndexOutput.writeBytes(randomBytes(100), 0, 100);
+            multiBlobIndexOutput.flush();
+        }
+
+        PropertyState jcrData = builder.getChildNode(":data").getChildNode("foo").getProperty("jcr:data");
+        assertTrue("multiple blobs not written", jcrData.isArray());
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            IndexOutput multiBlobIndexOutput = multiBlobDir.createOutput("foo", IOContext.DEFAULT);
+
+            multiBlobIndexOutput.writeBytes(randomBytes(100), 0, 100);
+            multiBlobIndexOutput.flush();
+        }
+
+        jcrData = builder.getChildNode(":data").getChildNode("foo").getProperty("jcr:data");
+        assertFalse("multiple blobs written", jcrData.isArray());
+
+        try (Directory multiBlobDir = createDir(builder, false)) {
+            IndexOutput multiBlobIndexOutput = multiBlobDir.createOutput("foo", IOContext.DEFAULT);
+
+            multiBlobIndexOutput.writeBytes(randomBytes(100), 0, 100);
+            multiBlobIndexOutput.flush();
+        }
+
+        jcrData = builder.getChildNode(":data").getChildNode("foo").getProperty("jcr:data");
+        assertTrue("multiple blobs not written despite disabled buffered directory", jcrData.isArray());
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(oldVal);
+    }
+
+    @Test
+    public void readNonStreamingWhenMultipleBlobsExist() throws Exception {
+        boolean oldVal = BufferedOakDirectory.isEnableWritingSingleBlobIndexFile();
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            IndexOutput multiBlobIndexOutput = multiBlobDir.createOutput("foo", IOContext.DEFAULT);
+
+            multiBlobIndexOutput.writeBytes(randomBytes(100), 0, 100);
+            multiBlobIndexOutput.flush();
+        }
+
+        // Enable feature... reader shouldn't care about the flag.
+        // Repo state needs to be used for that
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            OakIndexInput multiBlobIndexInput = (OakIndexInput)multiBlobDir.openInput("foo", IOContext.DEFAULT);
+
+            assertTrue("OakBufferedIndexFile must be used",
+                    multiBlobIndexInput.file instanceof OakBufferedIndexFile);
+        }
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(oldVal);
+    }
+
+    @Test
+    public void readStreamingWithSingleBlob() throws Exception {
+        boolean oldVal = BufferedOakDirectory.isEnableWritingSingleBlobIndexFile();
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            IndexOutput multiBlobIndexOutput = multiBlobDir.createOutput("foo", IOContext.DEFAULT);
+
+            multiBlobIndexOutput.writeBytes(randomBytes(100), 0, 100);
+            multiBlobIndexOutput.flush();
+        }
+
+        // Enable feature... reader shouldn't care about the flag.
+        // Repo state needs to be used for that
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            OakIndexInput multiBlobIndexInput = (OakIndexInput)multiBlobDir.openInput("foo", IOContext.DEFAULT);
+
+            assertTrue("OakStreamingIndexFile must be used",
+                    multiBlobIndexInput.file instanceof OakStreamingIndexFile);
+        }
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(oldVal);
+    }
+
+    @Test
+    public void writeNonStreamingIfDisabledByFlag() throws Exception {
+        boolean oldVal = BufferedOakDirectory.isEnableWritingSingleBlobIndexFile();
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            OakIndexOutput multiBlobIndexOutput = (OakIndexOutput)multiBlobDir.createOutput("foo1", IOContext.DEFAULT);
+
+            assertTrue("OakBufferedIndexFile must be used",
+                    multiBlobIndexOutput.file instanceof OakBufferedIndexFile);
+        }
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        try (Directory multiBlobDir = createDir(builder, true)) {
+            OakIndexOutput multiBlobIndexOutput = (OakIndexOutput)multiBlobDir.createOutput("foo2", IOContext.DEFAULT);
+
+            assertTrue("OakStreamingIndexFile must be used",
+                    multiBlobIndexOutput.file instanceof OakStreamingIndexFile);
+        }
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(oldVal);
+    }
+
+    @Test
+    public void defaultValue() {
+        BufferedOakDirectory bufferedOakDirectory = (BufferedOakDirectory)createDir(builder, true);
+        assertTrue("Flag not setting as set by command line flag",
+                bufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+    }
+
+    @Test
+    public void commandLineParamSetsValue() {
+        String oldVal = System.getProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "true");
+        reReadCommandLineParam();
+        assertTrue("Flag not setting as set by command line flag",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "false");
+        reReadCommandLineParam();
+        assertFalse("Flag not setting as set by command line flag",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+
+        if (oldVal == null) {
+            System.clearProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+        } else {
+            System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, oldVal);
+        }
+    }
+
+    @Test
+    public void commandLineOverridesSetter() {
+        String oldVal = System.getProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "true");
+        reReadCommandLineParam();
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        assertTrue("Flag not setting as set by command line flag",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "false");
+        reReadCommandLineParam();
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        assertFalse("Flag not setting as set by command line flag",
+                BufferedOakDirectory.isEnableWritingSingleBlobIndexFile());
+
+        if (oldVal == null) {
+            System.clearProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+        } else {
+            System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, oldVal);
+        }
+    }
+
+    @Test
+    public void settingConfigDifferentFromCLIWarns() {
+        String oldVal = System.getProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+
+        final LogCustomizer custom = LogCustomizer
+                .forLogger(BufferedOakDirectory.class.getName())
+                .contains("Ignoring configuration ")
+                .enable(Level.WARN).create();
+
+        custom.starting();
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "true");
+        reReadCommandLineParam();
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        assertEquals("Warn on conflicting config on CLI and set method", 1, custom.getLogs().size());
+        custom.finished();
+
+        custom.starting();
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "false");
+        reReadCommandLineParam();
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        assertEquals("Warn on conflicting config on CLI and set method", 1, custom.getLogs().size());
+        custom.finished();
+
+        if (oldVal == null) {
+            System.clearProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+        } else {
+            System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, oldVal);
+        }
+    }
+
+    @Test
+    public void dontWarnUnnecesarily() {
+        String oldVal = System.getProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+
+        final LogCustomizer custom = LogCustomizer
+                .forLogger(BufferedOakDirectory.class.getName())
+                .contains("Ignoring configuration ")
+                .enable(Level.WARN).create();
+
+        custom.starting();
+
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        assertEquals("Don't warn unnecessarily", 0, custom.getLogs().size());
+
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "true");
+        reReadCommandLineParam();
+        assertEquals("Don't warn unnecessarily", 0, custom.getLogs().size());
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(true);
+        assertEquals("Don't warn unnecessarily", 0, custom.getLogs().size());
+        System.clearProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+
+        System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, "false");
+        reReadCommandLineParam();
+        assertEquals("Don't warn unnecessarily", 0, custom.getLogs().size());
+        BufferedOakDirectory.setEnableWritingSingleBlobIndexFile(false);
+        assertEquals("Don't warn unnecessarily", 0, custom.getLogs().size());
+        System.clearProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+
+        custom.finished();
+
+        if (oldVal == null) {
+            System.clearProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM);
+        } else {
+            System.setProperty(ENABLE_WRITING_SINGLE_BLOB_INDEX_FILE_PARAM, oldVal);
+        }
+    }
+
     private void assertFile(Directory dir, String file, byte[] expected)
             throws IOException {
         assertTrue(dir.fileExists(file));

Added: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/OakStreamingIndexFileTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/OakStreamingIndexFileTest.java?rev=1810243&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/OakStreamingIndexFileTest.java (added)
+++ jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/OakStreamingIndexFileTest.java Sun Oct  1 00:42:18 2017
@@ -0,0 +1,321 @@
+/*
+ * 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.
+ */
+
+package org.apache.jackrabbit.oak.plugins.index.lucene.directory;
+
+import ch.qos.logback.classic.Level;
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.commons.junit.LogCustomizer;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.lucene.store.ByteArrayDataInput;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.OakStreamingIndexFileTest.BlobFactoryMode.BATCH_READ;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.OakStreamingIndexFileTest.BlobFactoryMode.BYTE_WISE_READ;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.OakStreamingIndexFileTest.FileCreateMode.COPY_BYTES;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.directory.OakStreamingIndexFileTest.FileCreateMode.WRITE_FILE;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(Parameterized.class)
+public class OakStreamingIndexFileTest {
+
+    private Random rnd = new Random();
+
+    private NodeState root = EMPTY_NODE;
+
+    private NodeBuilder builder = root.builder();
+
+    private int fileSize = 1000 + rnd.nextInt(1000);
+
+    private final FileCreateMode fileCreateMode;
+    private final BlobFactoryMode blobFactoryMode;
+    private final ModeDependantBlobFactory modeDependantBlobFactory;
+
+    enum BlobFactoryMode {
+        BATCH_READ,
+        BYTE_WISE_READ
+    }
+
+    enum FileCreateMode {
+        WRITE_FILE,
+        COPY_BYTES
+    }
+
+    @Parameterized.Parameters(name="{0}, {1}")
+    public static Collection<Object[]> fixtures() throws Exception {
+        List<Object[]> modes = Lists.newArrayList();
+        modes.add(new Object[]{COPY_BYTES, BYTE_WISE_READ});
+        modes.add(new Object[]{COPY_BYTES, BATCH_READ});
+        modes.add(new Object[]{WRITE_FILE, BYTE_WISE_READ});
+        modes.add(new Object[]{WRITE_FILE, BATCH_READ});
+        return modes;
+    }
+
+    public OakStreamingIndexFileTest(FileCreateMode fileCreateMode, BlobFactoryMode blobFactoryMode) {
+        this.fileCreateMode = fileCreateMode;
+        this.blobFactoryMode = blobFactoryMode;
+        this.modeDependantBlobFactory = new ModeDependantBlobFactory(blobFactoryMode);
+    }
+
+    @Test
+    public void readSanity() throws Exception {
+        byte[] fileBytes = writeFile();
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile readFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            byte[] readBytes = new byte[fileBytes.length];
+            readFile.readBytes(readBytes, 0, fileBytes.length);
+
+            assertEquals("Must read same amount of data", fileBytes.length, readBytes.length);
+            assertTrue("Must get back same data", Arrays.equals(readBytes, fileBytes));
+
+            try {
+                readFile.readBytes(readBytes, 0, 1);
+                fail("Must not be able to read past stored number of bytes");
+            } catch (Exception ignored) {
+                // ignore
+            }
+        }
+    }
+
+    @Test
+    public void rangeReadWorks() throws Exception {
+        int numFewBytes = 100;
+
+        byte[] fileBytes = writeFile();
+        byte[] aFewBytes = Arrays.copyOfRange(fileBytes, 1, numFewBytes + 1);
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile readFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            readFile.seek(1);
+            assertEquals("Seeking should move position", 1, readFile.position());
+
+            byte[] readBytes = new byte[numFewBytes];
+            readFile.readBytes(readBytes, 0, numFewBytes);
+            assertTrue("Reading a few bytes should be accurate", Arrays.equals(readBytes, aFewBytes));
+        }
+    }
+
+    @Test
+    public void rangeReadWorksOnSeekingBack() throws Exception {
+        int numFewBytes = 100;
+
+        byte[] fileBytes = writeFile();
+        byte[] aFewBytes = Arrays.copyOfRange(fileBytes, 1, numFewBytes + 1);
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile readFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            byte[] readBytes = new byte[numFewBytes];
+
+            readFile.seek(100);
+            readFile.readBytes(readBytes, 0, 1);
+            readFile.seek(1);
+            assertEquals("Seeking should move position", 1, readFile.position());
+
+            readFile.readBytes(readBytes, 0, numFewBytes);
+            assertTrue("Reading a few bytes should be accurate", Arrays.equals(readBytes, aFewBytes));
+        }
+    }
+
+    @Test
+    public void cloneCreatesSimilarUnrelatedStreams() throws Exception {
+        int numFewBytes = 100;
+
+        byte[] fileBytes = writeFile();
+        byte[] aFewBytes = Arrays.copyOfRange(fileBytes, 2, numFewBytes + 2);
+
+        byte[] readBytes = new byte[numFewBytes];
+
+        OakStreamingIndexFile readFileClone;
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile readFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            readFile.seek(1);
+            readFile.readBytes(readBytes, 0, 1);
+
+            readFileClone = (OakStreamingIndexFile) readFile.clone();
+
+            readFile.readBytes(readBytes, 0, numFewBytes);
+        }
+
+        assertNotNull("Clone reader should have been created", readFileClone);
+
+        try {
+            readFileClone.readBytes(readBytes, 0, numFewBytes);
+            assertTrue("Clone reader should start from same position as source",
+                    Arrays.equals(readBytes, aFewBytes));
+        } finally {
+            readFileClone.close();
+        }
+    }
+
+    @Test
+    public void streamingWritesDontWorkPiecewise() throws Exception {
+        Assume.assumeTrue("Piece write makes sense for " + WRITE_FILE + " mode", fileCreateMode == WRITE_FILE);
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile writeFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            byte[] fileBytes = randomBytes(fileSize);
+
+            writeFile.writeBytes(fileBytes, 0, fileBytes.length);
+            try {
+                writeFile.writeBytes(fileBytes, 0, 1);
+                fail("Multiple write bytes must not be allowed with streaming writes");
+            } catch (Exception ignored) {
+                //ignore
+            }
+            writeFile.flush();
+        }
+    }
+
+    @Test
+    public void seekScenarios() throws Exception {
+        byte[] fileBytes = writeFile();
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile readFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            byte[] aFewBytes = new byte[10];
+            byte[] expectedFewBytes;
+
+            expectedFewBytes = Arrays.copyOfRange(fileBytes, 10, 10 + aFewBytes.length);
+            readFile.seek(10);
+            readFile.readBytes(aFewBytes, 0, aFewBytes.length);
+            assertTrue("Range read after seek should read accurately",
+                    Arrays.equals(expectedFewBytes, aFewBytes));
+
+
+            expectedFewBytes = Arrays.copyOfRange(fileBytes, 25, 25 + aFewBytes.length);
+            readFile.seek(25);
+            readFile.readBytes(aFewBytes, 0, aFewBytes.length);
+            assertTrue("Range read after seek should read accurately",
+                    Arrays.equals(expectedFewBytes, aFewBytes));
+
+
+            expectedFewBytes = Arrays.copyOfRange(fileBytes, 2, 2 + aFewBytes.length);
+            readFile.seek(2);
+            readFile.readBytes(aFewBytes, 0, aFewBytes.length);
+            assertTrue("Range read after backward seek should read accurately",
+                    Arrays.equals(expectedFewBytes, aFewBytes));
+
+        }
+    }
+
+    @Test
+    public void logWarnWhenSeekingBackAfterRead() throws Exception {
+        byte[] fileBytes = writeFile();
+
+        LogCustomizer logRecorder = LogCustomizer
+                .forLogger(OakStreamingIndexFile.class.getName()).enable(Level.WARN)
+                .contains("Seeking back on streaming index file").create();
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile readFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            logRecorder.starting();
+            byte[] readBytes = new byte[fileBytes.length];
+
+            readFile.readBytes(readBytes, 0, 10);
+            assertEquals("Don't log for simple reads", 0, logRecorder.getLogs().size());
+
+            readFile.seek(12);
+            assertEquals("Don't log for forward seeks", 0, logRecorder.getLogs().size());
+
+            readFile.seek(2);
+            assertEquals("Log warning for backward seeks", 1, logRecorder.getLogs().size());
+        }
+
+        logRecorder.finished();
+    }
+
+    static class ModeDependantBlobFactory {
+
+        private final BlobFactoryMode blobFactoryMode;
+
+        ModeDependantBlobFactory(BlobFactoryMode blobFactoryMode) {
+            this.blobFactoryMode = blobFactoryMode;
+        }
+
+        BlobFactory getNodeBuilderBlobFactory(final NodeBuilder builder) {
+            final BlobFactory delegate = BlobFactory.getNodeBuilderBlobFactory(builder);
+            return in -> {
+                if (blobFactoryMode == BYTE_WISE_READ) {
+                    return delegate.createBlob(new InputStream() {
+                        @Override
+                        public int read() throws IOException {
+                            return in.read();
+                        }
+                    });
+                } else {
+                    return delegate.createBlob(in);
+                }
+            };
+        }
+    }
+
+    private byte[] writeFile() throws Exception {
+        byte[] fileBytes = randomBytes(fileSize);
+
+        NodeBuilder fooBuilder = builder.child("foo");
+        try (OakStreamingIndexFile writeFile = new OakStreamingIndexFile("foo", fooBuilder, "dirDetails",
+                modeDependantBlobFactory.getNodeBuilderBlobFactory(fooBuilder))
+        ) {
+            if (fileCreateMode == COPY_BYTES) {
+                writeFile.copyBytes(new ByteArrayDataInput(fileBytes), fileBytes.length);
+            } else {
+                writeFile.writeBytes(fileBytes, 0, fileBytes.length);
+            }
+            writeFile.flush();
+        }
+
+        return fileBytes;
+    }
+
+    private byte[] randomBytes(int size) {
+        byte[] data = new byte[size];
+        rnd.nextBytes(data);
+        return data;
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/directory/OakStreamingIndexFileTest.java
------------------------------------------------------------------------------
    svn:eol-style = native