Author: msahyoun
Date: Thu Apr 2 09:21:51 2026
New Revision: 1932732
Log:
PDFBOX-6185: enhanced unit test; parts by Claude Sonnet 4.6
Modified:
pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/TestIOUtils.java
Modified: pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/TestIOUtils.java
==============================================================================
--- pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/TestIOUtils.java Thu Apr
2 09:06:14 2026 (r1932731)
+++ pdfbox/trunk/io/src/test/java/org/apache/pdfbox/io/TestIOUtils.java Thu Apr
2 09:21:51 2026 (r1932732)
@@ -20,11 +20,25 @@
package org.apache.pdfbox.io;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Set;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
/**
@@ -35,7 +49,7 @@ class TestIOUtils
/**
* Tests {@link IOUtils#populateBuffer(java.io.InputStream, byte[]).
- * @throws IOException if an I/O error occursn
+ * @throws IOException if an I/O error occurs
*/
@Test
void testPopulateBuffer() throws IOException
@@ -59,4 +73,339 @@ class TestIOUtils
assertEquals(-1, in.read()); //EOD reached
}
+ /**
+ * Tests {@link IOUtils#populateBuffer(java.io.InputStream, byte[])} with
empty stream.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testPopulateBufferEmpty() throws IOException
+ {
+ byte[] buffer = new byte[10];
+ InputStream in = new ByteArrayInputStream(new byte[0]);
+ long count = IOUtils.populateBuffer(in, buffer);
+ assertEquals(0, count);
+ }
+
+ /**
+ * Tests {@link IOUtils#toByteArray(InputStream)}.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testToByteArray() throws IOException
+ {
+ byte[] data = "Test Data".getBytes();
+ byte[] result = IOUtils.toByteArray(new ByteArrayInputStream(data));
+ assertEquals(data.length, result.length);
+ assertEquals(new String(data), new String(result));
+ }
+
+ /**
+ * Tests {@link IOUtils#toByteArray(InputStream)} with empty stream.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testToByteArrayEmpty() throws IOException
+ {
+ byte[] result = IOUtils.toByteArray(new ByteArrayInputStream(new
byte[0]));
+ assertEquals(0, result.length);
+ }
+
+ /**
+ * Tests {@link IOUtils#toByteArray(InputStream)} with large data.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testToByteArrayLarge() throws IOException
+ {
+ byte[] data = new byte[10000];
+ for (int i = 0; i < data.length; i++)
+ {
+ data[i] = (byte)(i % 256);
+ }
+ byte[] result = IOUtils.toByteArray(new ByteArrayInputStream(data));
+ assertEquals(data.length, result.length);
+ }
+
+ /**
+ * Tests {@link IOUtils#copy(InputStream, OutputStream)}.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCopy() throws IOException
+ {
+ byte[] data = "Copy Test Content".getBytes();
+ InputStream input = new ByteArrayInputStream(data);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ long copied = IOUtils.copy(input, output);
+
+ assertEquals(data.length, copied);
+ assertEquals(new String(data), output.toString());
+ }
+
+ /**
+ * Tests {@link IOUtils#copy(InputStream, OutputStream)} with empty stream.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCopyEmpty() throws IOException
+ {
+ InputStream input = new ByteArrayInputStream(new byte[0]);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ long copied = IOUtils.copy(input, output);
+
+ assertEquals(0, copied);
+ assertEquals(0, output.size());
+ }
+
+ /**
+ * Tests {@link IOUtils#copy(InputStream, OutputStream)} with large data.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCopyLarge() throws IOException
+ {
+ byte[] data = new byte[50000];
+ for (int i = 0; i < data.length; i++)
+ {
+ data[i] = (byte)(i % 256);
+ }
+ InputStream input = new ByteArrayInputStream(data);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ long copied = IOUtils.copy(input, output);
+
+ assertEquals(data.length, copied);
+ assertEquals(data.length, output.size());
+ }
+
+ /**
+ * Tests {@link IOUtils#closeQuietly(Closeable)} with null closeable.
+ */
+ @Test
+ void testCloseQuietlyNull()
+ {
+ // Should not throw exception
+ assertDoesNotThrow(() -> IOUtils.closeQuietly(null));
+ }
+
+ /**
+ * Tests {@link IOUtils#closeQuietly(Closeable)} with valid closeable.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCloseQuietly() throws IOException
+ {
+ ByteArrayInputStream stream = new ByteArrayInputStream(new byte[10]);
+ assertDoesNotThrow(() -> IOUtils.closeQuietly(stream));
+ }
+
+ /**
+ * Tests {@link IOUtils#closeQuietly(Closeable)} suppresses exceptions.
+ */
+ @Test
+ void testCloseQuietlySuppressesException()
+ {
+ // Should not throw exception even if close() throws
+ assertDoesNotThrow(() -> {
+ Closeable failingCloseable = () -> {
+ throw new IOException("Test IOException");
+ };
+ IOUtils.closeQuietly(failingCloseable);
+ });
+ }
+
+ /**
+ * Tests {@link IOUtils#closeAndLogException(Closeable, Logger, String,
IOException)}
+ * with successful close and no initial exception.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCloseAndLogExceptionSuccess() throws IOException
+ {
+ Logger logger = LogManager.getLogger(TestIOUtils.class);
+ ByteArrayInputStream stream = new ByteArrayInputStream(new byte[10]);
+
+ IOException result = IOUtils.closeAndLogException(stream, logger,
"testResource", null);
+
+ assertNull(result);
+ }
+
+ /**
+ * Tests {@link IOUtils#closeAndLogException(Closeable, Logger, String,
IOException)}
+ * with close exception and no initial exception.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCloseAndLogExceptionCloseThrows() throws IOException
+ {
+ Logger logger = LogManager.getLogger(TestIOUtils.class);
+ IOException closeException = new IOException("Close error");
+
+ Closeable failingCloseable = () -> {
+ throw closeException;
+ };
+
+ IOException result = IOUtils.closeAndLogException(failingCloseable,
logger, "testResource", null);
+
+ assertEquals(closeException, result);
+ }
+
+ /**
+ * Tests {@link IOUtils#closeAndLogException(Closeable, Logger, String,
IOException)}
+ * preserves initial exception even if close also throws.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCloseAndLogExceptionPreservesInitialException() throws IOException
+ {
+ Logger logger = LogManager.getLogger(TestIOUtils.class);
+ IOException initialException = new IOException("Initial error");
+
+ Closeable failingCloseable = () -> {
+ throw new IOException("Close error");
+ };
+
+ IOException result = IOUtils.closeAndLogException(failingCloseable,
logger, "testResource", initialException);
+
+ assertEquals(initialException, result);
+ }
+
+ /**
+ * Tests {@link IOUtils#unmap(ByteBuffer)} with null buffer.
+ */
+ @Test
+ void testUnmapNull()
+ {
+ // Should not throw exception
+ assertDoesNotThrow(() -> IOUtils.unmap(null));
+ }
+
+ /**
+ * Tests {@link IOUtils#unmap(ByteBuffer)} with heap buffer.
+ */
+ @Test
+ void testUnmapHeapBuffer()
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ // Should not throw exception - heap buffers can be unmapped safely
(no-op)
+ assertDoesNotThrow(() -> IOUtils.unmap(buffer));
+ }
+
+ /**
+ * Tests {@link IOUtils#createMemoryOnlyStreamCache()} returns non-null
function.
+ */
+ @Test
+ void testCreateMemoryOnlyStreamCache()
+ {
+ RandomAccessStreamCache.StreamCacheCreateFunction function =
IOUtils.createMemoryOnlyStreamCache();
+ assertNotNull(function);
+ }
+
+ /**
+ * Tests {@link IOUtils#createTempFileOnlyStreamCache()} returns non-null
function.
+ */
+ @Test
+ void testCreateTempFileOnlyStreamCache()
+ {
+ RandomAccessStreamCache.StreamCacheCreateFunction function =
IOUtils.createTempFileOnlyStreamCache();
+ assertNotNull(function);
+ }
+
+ /**
+ * Tests {@link IOUtils#createProtectedTempDir()} creates a directory.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCreateProtectedTempDir() throws IOException
+ {
+ Path tempDir = IOUtils.createProtectedTempDir();
+ try
+ {
+ assertTrue(Files.exists(tempDir), "Temporary directory should
exist");
+ assertTrue(Files.isDirectory(tempDir), "Path should be a
directory");
+ assertTrue(tempDir.getFileName().toString().startsWith("pdfbox-"),
+ "Directory name should start with 'pdfbox-'");
+ }
+ finally
+ {
+ // Cleanup - note: shutdown hook should also handle this
+ if (Files.exists(tempDir))
+ {
+ Files.delete(tempDir);
+ }
+ }
+ }
+
+ /**
+ * Tests {@link IOUtils#createProtectedTempDir()} with POSIX permissions.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCreateProtectedTempDirPermissions() throws IOException
+ {
+ Path tempDir = IOUtils.createProtectedTempDir();
+ try
+ {
+ // Check if system supports POSIX permissions
+ if (Files.getFileStore(tempDir).supportsFileAttributeView("posix"))
+ {
+ Set<PosixFilePermission> perms =
Files.getPosixFilePermissions(tempDir);
+
+ // Should have owner read, write, execute
+ assertTrue(perms.contains(PosixFilePermission.OWNER_READ));
+ assertTrue(perms.contains(PosixFilePermission.OWNER_WRITE));
+ assertTrue(perms.contains(PosixFilePermission.OWNER_EXECUTE));
+
+ // Should NOT have group or others permissions
+ assertFalse(perms.contains(PosixFilePermission.GROUP_READ));
+ assertFalse(perms.contains(PosixFilePermission.GROUP_WRITE));
+ assertFalse(perms.contains(PosixFilePermission.GROUP_EXECUTE));
+ assertFalse(perms.contains(PosixFilePermission.OTHERS_READ));
+ assertFalse(perms.contains(PosixFilePermission.OTHERS_WRITE));
+
assertFalse(perms.contains(PosixFilePermission.OTHERS_EXECUTE));
+ }
+ }
+ finally
+ {
+ // Cleanup
+ if (Files.exists(tempDir))
+ {
+ Files.delete(tempDir);
+ }
+ }
+ }
+
+ /**
+ * Tests {@link IOUtils#createProtectedTempDir()} creates multiple unique
directories.
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ void testCreateProtectedTempDirMultiple() throws IOException
+ {
+ Path tempDir1 = IOUtils.createProtectedTempDir();
+ Path tempDir2 = IOUtils.createProtectedTempDir();
+
+ try
+ {
+ assertTrue(Files.exists(tempDir1));
+ assertTrue(Files.exists(tempDir2));
+ // Paths should be different
+ assertFalse(tempDir1.equals(tempDir2));
+ }
+ finally
+ {
+ // Cleanup
+ if (Files.exists(tempDir1))
+ {
+ Files.delete(tempDir1);
+ }
+ if (Files.exists(tempDir2))
+ {
+ Files.delete(tempDir2);
+ }
+ }
+ }
+
}