This is an automated email from the ASF dual-hosted git repository. afs pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit c856c0c5ea3bd93da523d24f101795164dab9842 Author: Andy Seaborne <[email protected]> AuthorDate: Thu Apr 30 11:23:22 2026 +0100 GH-3877: Add truncation tests to o.a.j.dboe.base.file.TS_File --- .../org/apache/jena/dboe/base/file/TS_File.java | 2 + .../base/file/TestBinaryDataFileRandomAccess.java | 186 +++++++++++++++++++++ .../TestBinaryDataFileRandomAccessTruncate.java | 13 +- 3 files changed, 195 insertions(+), 6 deletions(-) diff --git a/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TS_File.java b/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TS_File.java index f5aa2a7f2e..f5f29b06d0 100644 --- a/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TS_File.java +++ b/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TS_File.java @@ -41,6 +41,8 @@ import org.junit.platform.suite.api.Suite; , TestBinaryDataRAFInitial.class , TestBinaryDataRAF.class , TestBinaryDataFileWriteBufferedFile.class + , TestBinaryDataFileRandomAccessTruncate.class + , TestBinaryDataFileRandomAccess.class , TestProcessFileLock.class }) diff --git a/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccess.java b/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccess.java new file mode 100644 index 0000000000..fdacd38984 --- /dev/null +++ b/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccess.java @@ -0,0 +1,186 @@ +/* + * 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 + * + * https://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. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.apache.jena.dboe.base.file; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.file.Path; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.apache.jena.atlas.RuntimeIOException; + +public class TestBinaryDataFileRandomAccess { + + @TempDir Path tempDir; + + @Test public void bad_truncate() { + Path file = tempDir.resolve("truncate1"); + BinaryDataFileRandomAccess subject = new BinaryDataFileRandomAccess(file.toString()); + subject.open(); + try { + // Put some thing in the file. + byte[] block = filled(1024, (byte) 0xAB); + subject.write(block); + // Attempt to truncate outside the file. + assertThrows(RuntimeIOException.class, ()->subject.truncate(2048)); + } finally { subject.close(); } + } + + @Test public void bad_seek_read() { + Path file = tempDir.resolve("truncate2"); + BinaryDataFileRandomAccess subject = new BinaryDataFileRandomAccess(file.toString()); + subject.open(); + try { + byte[] block = filled(1024, (byte) 0xAB); + subject.write(block); + + byte[] block2 = new byte[10]; + // OK + subject.read(512, block2); + // Attempt to read at end of file. + // OK + subject.read(1024, block2); + + // Attempt to read starting beyond end of file. + assertThrows(RuntimeIOException.class, ()->subject.read(2048, block2)); + + subject.truncate(512); + // Now not OK. + assertThrows(RuntimeIOException.class, ()->subject.read(1024, block2)); + } finally { subject.close(); } + } + + + private static byte[] filled(int size, byte v) { + byte[] b = new byte[size]; + Arrays.fill(b, v); + return b; + } + + +} +// +// private static final String FILE = "target/test-bdfra-truncate-writepos"; +// +// private BinaryDataFile file; +// +// @BeforeEach public void before() { +// FileOps.delete(FILE); +// file = new BinaryDataFileRandomAccess(FILE); +// file.open(); +// } +// +// @AfterEach public void after() { +// file.close(); +// FileOps.delete(FILE); +// } +// +// /** +// * truncate() then write() (no intervening read) — file must contain only +// * the written bytes up to the new length. +// */ +// @Test public void truncate_then_write_no_gap() { +// byte[] block = filled(300 * 1024, (byte) 0xAB); +// file.write(block); +// assertEquals(300 * 1024L, file.length()); +// +// file.truncate(100 * 1024); +// assertEquals(100 * 1024L, file.length()); +// +// byte[] tail = filled(100, (byte) 0xCD); +// long writtenAt = file.write(tail); +// assertEquals(100 * 1024L, writtenAt, +// "write() after truncate() must start at the truncated length, not at a stale writePosition"); +// assertEquals(100 * 1024L + 100, file.length()); +// +// assertNoZeroRun(FILE); +// } +// +// /** +// * truncate() then read() then write() — the read flips the internal +// * readMode flag, so the subsequent write goes through switchToWriteMode +// * and seeks to the stored writePosition. Without the fix, that position +// * is stale and writes past the truncated EOF, zero-filling the gap. +// */ +// @Test public void truncate_then_read_then_write_no_gap() { +// byte[] block = filled(300 * 1024, (byte) 0xAB); +// file.write(block); +// +// file.truncate(100 * 1024); +// assertEquals(100 * 1024L, file.length()); +// +// byte[] sample = new byte[16]; +// file.read(0, sample); +// +// byte[] tail = filled(100, (byte) 0xCD); +// long writtenAt = file.write(tail); +// assertEquals(100 * 1024L, writtenAt, +// "write() after truncate()+read() must start at the truncated length, not at a stale writePosition"); +// assertEquals(100 * 1024L + 100, file.length()); +// +// assertNoZeroRun(FILE); +// } +// +// private static byte[] filled(int size, byte v) { +// byte[] b = new byte[size]; +// Arrays.fill(b, v); +// return b; +// } +// +// /** +// * Fail the enclosing test if the on-disk file contains a contiguous run +// * of zero bytes of length >= 16. The source blocks are all 0xAB/0xCD so +// * any zero byte is evidence of filesystem zero-fill past a stale seek. +// */ +// private static void assertNoZeroRun(String path) { +// try (RandomAccessFile raf = new RandomAccessFile(path, "r")) { +// long total = raf.length(); +// byte[] buf = new byte[(int) total]; +// raf.readFully(buf); +// int runStart = -1; +// int runLen = 0; +// for (int i = 0; i < buf.length; i++) { +// if (buf[i] == 0) { +// if (runStart < 0) { runStart = i; runLen = 1; } +// else runLen++; +// } else { +// if (runLen >= 16) { +// assertTrue(false, +// "Zero-byte run of " + runLen + " bytes starting at offset " + runStart +// + " indicates a filesystem zero-fill (stale writePosition after truncate)."); +// } +// runStart = -1; +// runLen = 0; +// } +// } +// if (runLen >= 16) { +// assertTrue(false, +// "Zero-byte run of " + runLen + " bytes at end of file starting at offset " + runStart); +// } +// } catch (IOException ex) { +// throw new RuntimeException(ex); +// } +// } +//} diff --git a/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccessTruncate.java b/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccessTruncate.java index 233325ef1a..3aadc2d182 100644 --- a/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccessTruncate.java +++ b/jena-db/jena-dboe-base/src/test/java/org/apache/jena/dboe/base/file/TestBinaryDataFileRandomAccessTruncate.java @@ -26,12 +26,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.file.Path; import java.util.Arrays; -import org.apache.jena.atlas.lib.FileOps; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; /** * Regression test: {@link BinaryDataFileRandomAccess#truncate(long)} must reset @@ -51,19 +52,19 @@ import org.junit.jupiter.api.Test; */ public class TestBinaryDataFileRandomAccessTruncate { - private static final String FILE = "target/test-bdfra-truncate-writepos"; - - private BinaryDataFile file; + @TempDir Path tempDir; + private BinaryDataFile file = null; + private String FILE = null; + private static int counter = 0 ; @BeforeEach public void before() { - FileOps.delete(FILE); + FILE = tempDir.resolve("truncate-"+(++counter)).toString(); file = new BinaryDataFileRandomAccess(FILE); file.open(); } @AfterEach public void after() { file.close(); - FileOps.delete(FILE); } /**
