thecoop commented on code in PR #16619:
URL: https://github.com/apache/lucene/pull/16619#discussion_r3914231987


##########
lucene/misc/src/test/org/apache/lucene/misc/store/TestDirectIODirectory.java:
##########
@@ -218,6 +219,245 @@ public void testUseDirectIODefaults() throws Exception {
     }
   }
 
+  private static byte[] writeRandomFile(Directory dir, String name, int size) 
throws IOException {
+    byte[] bytes = new byte[size];
+    random().nextBytes(bytes);
+    try (IndexOutput o = dir.createOutput(name, IOContext.DEFAULT)) {
+      o.writeBytes(bytes, 0, size);
+    }
+    return bytes;
+  }
+
+  public void testSliceDefersIOAtEveryOffset() throws Exception {
+    Path path = createTempDir("testSliceDefersIOAtEveryOffset");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    final int fileSize = 4 * blockSize;
+    try (Directory dir = getDirectory(path)) {
+      byte[] bytes = writeRandomFile(dir, "out", fileSize);
+      final long[] offsets = {
+        0, 1, 7, 96, blockSize, blockSize + 188, 2L * blockSize, 3L * 
blockSize - 4
+      };
+      try (IndexInput in = dir.openInput("out", IOContext.DEFAULT)) {
+        for (long offset : offsets) {
+          IndexInput slice = in.slice("slice@" + offset, offset, fileSize - 
offset);
+          DirectIODirectory.DirectIOIndexInput directSlice =
+              (DirectIODirectory.DirectIOIndexInput) slice;
+          assertTrue(
+              "slice at offset " + offset + " did not defer its first fill",
+              directSlice.isDeferred());
+          assertEquals(0L, slice.getFilePointer());
+          assertEquals(
+              "first byte of slice at offset " + offset, bytes[(int) offset], 
slice.readByte());
+          assertFalse(directSlice.isDeferred());
+        }
+      }
+    }
+  }
+
+  public void testSliceCorrectnessMatrix() throws Exception {
+    Path path = createTempDir("testSliceCorrectnessMatrix");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    final int fileSize = 4 * blockSize;
+    try (Directory dir = getDirectory(path)) {
+      byte[] bytes = writeRandomFile(dir, "out", fileSize);
+      final long[] offsets = {
+        0, 1, 7, 96, blockSize, blockSize + 188, 2L * blockSize, 3L * 
blockSize - 4
+      };
+      try (IndexInput in = dir.openInput("out", IOContext.DEFAULT)) {
+        for (long offset : offsets) {
+          final long[] lengths = {1, 4, blockSize - 7, blockSize, blockSize + 
3, fileSize - offset};
+          for (long length : lengths) {
+            if (length <= 0 || length > fileSize - offset) {
+              continue;
+            }
+            final int len = (int) length;
+            // full read-through of the slice equals the same range of the 
parent
+            IndexInput slice = in.slice("s", offset, length);
+            byte[] actual = new byte[len];
+            slice.readBytes(actual, 0, len);
+            assertArrayEquals(
+                "offset " + offset + " length " + length,
+                ArrayUtil.copyOfSubArray(bytes, (int) offset, (int) offset + 
len),
+                actual);
+            if (len >= 2) {
+              // slice-of-slice at a non-zero inner offset
+              IndexInput inner = in.slice("s", offset, length).slice("ss", 1, 
length - 1);
+              byte[] innerBytes = new byte[len - 1];
+              inner.readBytes(innerBytes, 0, len - 1);
+              assertArrayEquals(
+                  "inner slice at offset " + offset + " length " + length,
+                  ArrayUtil.copyOfSubArray(bytes, (int) offset + 1, (int) 
offset + len),
+                  innerBytes);
+              // clone-after-partial-read starts at the parent's position
+              IndexInput partial = in.slice("s", offset, length);
+              partial.readBytes(new byte[len / 2], 0, len / 2);
+              IndexInput clone = partial.clone();
+              assertEquals(len / 2, clone.getFilePointer());
+              byte[] rest = new byte[len - len / 2];
+              clone.readBytes(rest, 0, rest.length);
+              assertArrayEquals(
+                  "clone at offset " + offset + " length " + length,
+                  ArrayUtil.copyOfSubArray(bytes, (int) offset + len / 2, 
(int) offset + len),
+                  rest);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  public void testSeekResolvesFreshParkedSlice() throws Exception {
+    Path path = createTempDir("testSeekResolvesFreshParkedSlice");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    final int fileSize = DirectIODirectory.DEFAULT_MERGE_BUFFER_SIZE + 3 * 
blockSize;
+    try (Directory dir = getDirectory(path)) {
+      byte[] bytes = writeRandomFile(dir, "out", fileSize);
+      final long[] offsets = {1, 96, blockSize + 188};
+      try (IndexInput in = dir.openInput("out", IOContext.DEFAULT)) {
+        for (long offset : offsets) {
+          final long length =
+              Math.min(DirectIODirectory.DEFAULT_MERGE_BUFFER_SIZE, fileSize - 
offset);
+          IndexInput slice = in.slice("s", offset, length);
+          // seek() on a slice that has not been read yet must position it 
correctly: the byte
+          // read is the slice's own last byte, not one relative to the wrong 
base.
+          slice.seek(length - 1);
+          assertEquals("offset " + offset, bytes[(int) (offset + length - 1)], 
slice.readByte());
+          assertEquals(length, slice.getFilePointer());
+        }
+      }
+    }
+  }
+
+  public void testCloneOfPartiallyReadUnalignedSliceResolvesToItsPosition() 
throws Exception {
+    // clone() positions a fresh clone at the parent's file pointer through 
seekInternal, and is
+    // the only caller that reaches it with a pending start. If seekInternal 
did not drop that
+    // pending start first, a clone taken after a partial read of a short 
unaligned slice would
+    // read the wrong bytes or throw EOFException.
+    Path path = createTempDir("testCloneOfPartiallyReadSlice");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    final int fileSize = 4 * blockSize;
+    try (Directory dir = getDirectory(path)) {
+      byte[] bytes = writeRandomFile(dir, "out", fileSize);
+      final long[] offsets = {1, 96, blockSize + 188, 3L * blockSize - 4};
+      try (IndexInput in = dir.openInput("out", IOContext.DEFAULT)) {
+        for (long offset : offsets) {
+          final long length = Math.min(blockSize - 7, fileSize - offset);
+          IndexInput partial = in.slice("s", offset, length);
+          final int half = (int) length / 2;
+          partial.readBytes(new byte[half], 0, half);
+          IndexInput clone = partial.clone();
+          assertEquals("offset " + offset, half, clone.getFilePointer());
+          byte[] rest = new byte[(int) length - half];
+          clone.readBytes(rest, 0, rest.length);
+          assertArrayEquals(
+              "offset " + offset,
+              ArrayUtil.copyOfSubArray(bytes, (int) offset + half, (int) 
offset + (int) length),
+              rest);
+        }
+      }
+    }
+  }
+
+  public void testFirstFillEofSemanticsMatchEagerFill() throws Exception {
+    Path path = createTempDir("testFirstFillEofSemantics");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    final int fileSize = 4 * blockSize;
+    // the oracle below relies on the whole file fitting in one buffer window
+    assumeTrue(
+        "file must fit in one buffer window",
+        fileSize <= DirectIODirectory.DEFAULT_MERGE_BUFFER_SIZE);
+    try (Directory dir = getDirectory(path)) {
+      byte[] bytes = writeRandomFile(dir, "out", fileSize);
+      final long[] offsets = {0, 96, blockSize, fileSize - 5};
+      final long[] lengths = {0, 1, 4, blockSize};
+      try (IndexInput in = dir.openInput("out", IOContext.DEFAULT)) {
+        for (long offset : offsets) {
+          for (long l : lengths) {
+            final long length = Math.min(l, fileSize - offset);
+            final int[] readLens = {1, 4, (int) length + 2, blockSize + 32, 
fileSize};
+            for (int readLen : readLens) {
+              IndexInput slice = in.slice("s", offset, length);
+              final String cell = "offset=" + offset + " length=" + length + " 
readLen=" + readLen;
+              // Pins today's behaviour, quirk included: bytes inside the 
buffered window are
+              // served even past the slice's own length, and only a read that 
runs off the end
+              // of the window throws, as EOFException. If the first fill ever 
checked EOF against
+              // the caller's read length instead, the in-window cells below 
would fail.
+              if (readLen <= fileSize - offset) {
+                byte[] dst = new byte[readLen];
+                slice.readBytes(dst, 0, readLen);
+                assertArrayEquals(
+                    cell,
+                    ArrayUtil.copyOfSubArray(bytes, (int) offset, (int) offset 
+ readLen),
+                    dst);
+              } else {
+                expectThrows(
+                    EOFException.class, cell, () -> slice.readBytes(new 
byte[readLen], 0, readLen));
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  public void testZeroLengthSliceAtFileEndThrowsEOF() throws Exception {
+    Path path = createTempDir("testZeroLengthSliceAtFileEnd");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    // one file whose length is a multiple of the block size (pending offset 
0) and one whose
+    // length is not
+    final int[] fileSizes = {2 * blockSize, 2 * blockSize + 300};
+    try (Directory dir = getDirectory(path)) {
+      for (int fileSize : fileSizes) {
+        String name = "out" + fileSize;
+        writeRandomFile(dir, name, fileSize);
+        try (IndexInput in = dir.openInput(name, IOContext.DEFAULT)) {
+          IndexInput slice = in.slice("end", in.length(), 0);
+          // the exception type matters: a first fill that finds nothing to 
read must fall
+          // through to the caller's own read and throw EOFException, not a
+          // BufferUnderflowException from an empty buffer
+          expectThrows(EOFException.class, "file size " + fileSize, 
slice::readByte);
+        }
+      }
+    }
+  }
+
+  public void testSeekBeyondSliceLengthMatchesEagerBehaviour() throws 
Exception {
+    Path path = createTempDir("testSeekBeyondSliceLength");
+    final int blockSize = 
Math.toIntExact(Files.getFileStore(path).getBlockSize());
+    final int fileSize = 4 * blockSize;
+    assumeTrue(
+        "file must fit in one buffer window",
+        fileSize <= DirectIODirectory.DEFAULT_MERGE_BUFFER_SIZE);
+    try (Directory dir = getDirectory(path)) {
+      byte[] bytes = writeRandomFile(dir, "out", fileSize);
+      try (IndexInput in = dir.openInput("out", IOContext.DEFAULT)) {
+        // seek targets inside the block the eager code buffered at 
construction: today the
+        // seek silently repositions and the read serves the byte beyond the 
slice's end. A
+        // fresh lazy slice must do the same rather than throw EOFException, 
and
+        // getFilePointer() must return normally afterwards (assertions are 
enabled)
+        final long[] offsets = {blockSize, 700};
+        final long[] seekTargets = {10, blockSize + 50L};
+        for (long offset : offsets) {
+          for (long target : seekTargets) {
+            IndexInput slice = in.slice("s", offset, 4);
+            slice.seek(target);
+            assertEquals(
+                "offset " + offset + " target " + target,
+                bytes[(int) (offset + target)],
+                slice.readByte());
+            assertEquals(target + 1, slice.getFilePointer());
+          }
+        }
+        // a target outside the buffered window: the refill checks EOF against 
the slice's own
+        // end and throws, as today; getFilePointer() must still return 
normally afterwards
+        IndexInput far = in.slice("s", 700, 4);
+        expectThrows(
+            EOFException.class, () -> 
far.seek(DirectIODirectory.DEFAULT_MERGE_BUFFER_SIZE + 100L));
+        assertTrue(far.getFilePointer() >= 0);

Review Comment:
   Using hamcrest - `assertThat(far.getFilePointer(), greaterThanOrEqualTo(0))`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to