Skip to content

Commit f542fc4

Browse files
author
Maruan Sahyoun
committed
PDFBOX-6166: additional test coverage by Claude Sonnet
git-svn-id: https://svn.apache.org/repos/asf/pdfbox/trunk@1932994 13f79535-47bb-0310-9956-ffa450edef68
1 parent 2f92931 commit f542fc4

1 file changed

Lines changed: 167 additions & 0 deletions

File tree

io/src/test/java/org/apache/pdfbox/io/NonSeekableRandomAccessReadInputStreamTest.java

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.junit.jupiter.api.Assertions.assertTrue;
2222

2323
import java.io.ByteArrayInputStream;
24+
import java.io.EOFException;
2425
import java.io.IOException;
2526
import java.io.OutputStream;
2627

@@ -288,6 +289,172 @@ void testAccessClosed() throws IOException
288289
"read() should have thrown an IOException");
289290
}
290291

292+
/**
293+
* Verify that all methods which require an open stream throw IOException after close().
294+
*/
295+
@Test
296+
void testClosedStreamMethods() throws IOException
297+
{
298+
ByteArrayInputStream bais = new ByteArrayInputStream(new byte[] { 1, 2, 3 });
299+
NonSeekableRandomAccessReadInputStream rar =
300+
new NonSeekableRandomAccessReadInputStream(bais);
301+
rar.close();
302+
303+
Assertions.assertThrows(IOException.class, rar::read,
304+
"read() on closed stream should throw IOException");
305+
Assertions.assertThrows(IOException.class, () -> rar.read(new byte[1], 0, 1),
306+
"read(byte[], int, int) on closed stream should throw IOException");
307+
Assertions.assertThrows(IOException.class, () -> rar.readFully(new byte[1], 0, 1),
308+
"readFully() on closed stream should throw IOException");
309+
Assertions.assertThrows(IOException.class, rar::getPosition,
310+
"getPosition() on closed stream should throw IOException");
311+
Assertions.assertThrows(IOException.class, rar::available,
312+
"available() on closed stream should throw IOException");
313+
Assertions.assertThrows(IOException.class, rar::length,
314+
"length() on closed stream should throw IOException");
315+
Assertions.assertThrows(IOException.class, rar::isEOF,
316+
"isEOF() on closed stream should throw IOException");
317+
}
318+
319+
/**
320+
* Verify parameter validation in read(byte[], int, int) as required by the InputStream contract.
321+
*/
322+
@Test
323+
void testReadBytesParameterValidation() throws IOException
324+
{
325+
byte[] inputValues = { 0, 1, 2, 3, 4 };
326+
ByteArrayInputStream bais = new ByteArrayInputStream(inputValues);
327+
try (NonSeekableRandomAccessReadInputStream rar =
328+
new NonSeekableRandomAccessReadInputStream(bais))
329+
{
330+
// null buffer must throw NullPointerException
331+
Assertions.assertThrows(NullPointerException.class, () -> rar.read(null, 0, 1),
332+
"null buffer should throw NullPointerException");
333+
334+
byte[] buf = new byte[4];
335+
336+
// negative offset must throw IndexOutOfBoundsException
337+
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rar.read(buf, -1, 2),
338+
"negative offset should throw IndexOutOfBoundsException");
339+
340+
// negative length must throw IndexOutOfBoundsException
341+
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rar.read(buf, 0, -1),
342+
"negative length should throw IndexOutOfBoundsException");
343+
344+
// offset + length beyond buffer end must throw IndexOutOfBoundsException
345+
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> rar.read(buf, 2, 4),
346+
"offset + length > buf.length should throw IndexOutOfBoundsException");
347+
348+
// length == 0 must return 0 immediately without advancing position
349+
assertEquals(0, rar.read(buf, 0, 0));
350+
assertEquals(0, rar.getPosition());
351+
}
352+
}
353+
354+
/**
355+
* Verify that readFully() reads exactly the requested number of bytes across a buffer boundary.
356+
*/
357+
@Test
358+
void testReadFully() throws IOException
359+
{
360+
byte[] inputValues = new byte[10];
361+
for (int i = 0; i < inputValues.length; i++)
362+
{
363+
inputValues[i] = (byte) i;
364+
}
365+
ByteArrayInputStream bais = new ByteArrayInputStream(inputValues);
366+
try (NonSeekableRandomAccessReadInputStream rar =
367+
new NonSeekableRandomAccessReadInputStream(bais))
368+
{
369+
byte[] buf = new byte[10];
370+
rar.readFully(buf, 0, 10);
371+
for (int i = 0; i < 10; i++)
372+
{
373+
assertEquals(i, buf[i]);
374+
}
375+
assertEquals(10, rar.getPosition());
376+
}
377+
}
378+
379+
/**
380+
* Verify that readFully() throws EOFException when the stream ends before the requested
381+
* number of bytes are available.
382+
*/
383+
@Test
384+
void testReadFullyEOF() throws IOException
385+
{
386+
byte[] inputValues = { 0, 1, 2 };
387+
ByteArrayInputStream bais = new ByteArrayInputStream(inputValues);
388+
try (NonSeekableRandomAccessReadInputStream rar =
389+
new NonSeekableRandomAccessReadInputStream(bais))
390+
{
391+
Assertions.assertThrows(EOFException.class, () -> rar.readFully(new byte[10], 0, 10),
392+
"readFully() should throw EOFException when stream ends before length bytes");
393+
}
394+
}
395+
396+
/**
397+
* Verify that skip() silently stops at EOF without throwing an exception.
398+
*/
399+
@Test
400+
void testSkipPastEOF() throws IOException
401+
{
402+
byte[] inputValues = { 0, 1, 2, 3, 4 };
403+
ByteArrayInputStream bais = new ByteArrayInputStream(inputValues);
404+
try (NonSeekableRandomAccessReadInputStream rar =
405+
new NonSeekableRandomAccessReadInputStream(bais))
406+
{
407+
// skipping far beyond the end of the stream should not throw
408+
rar.skip(100);
409+
assertEquals(5, rar.getPosition());
410+
assertTrue(rar.isEOF());
411+
}
412+
}
413+
414+
/**
415+
* Verify that available() accounts for bytes buffered internally as well as bytes remaining
416+
* in the underlying stream, and returns 0 at EOF.
417+
*/
418+
@Test
419+
void testAvailable() throws IOException
420+
{
421+
byte[] inputValues = new byte[10];
422+
ByteArrayInputStream bais = new ByteArrayInputStream(inputValues);
423+
try (NonSeekableRandomAccessReadInputStream rar =
424+
new NonSeekableRandomAccessReadInputStream(bais))
425+
{
426+
// before any read, available() reflects is.available() since nothing is buffered yet
427+
assertEquals(10, rar.available());
428+
429+
// read one byte: the fetch pulls all 10 bytes into the internal buffer,
430+
// so available = 9 buffered + 0 remaining in the underlying stream
431+
rar.read();
432+
assertEquals(9, rar.available());
433+
434+
// consume all remaining bytes
435+
while (rar.read() != -1) {}
436+
assertEquals(0, rar.available());
437+
}
438+
}
439+
440+
/**
441+
* Verify that length() returns the exact total after the stream is fully consumed,
442+
* at which point size holds the true count and is.available() is 0.
443+
*/
444+
@Test
445+
void testLengthAfterFullConsumption() throws IOException
446+
{
447+
byte[] inputValues = new byte[100];
448+
ByteArrayInputStream bais = new ByteArrayInputStream(inputValues);
449+
try (NonSeekableRandomAccessReadInputStream rar =
450+
new NonSeekableRandomAccessReadInputStream(bais))
451+
{
452+
while (rar.read() != -1) {}
453+
assertTrue(rar.isEOF());
454+
assertEquals(100, rar.length());
455+
}
456+
}
457+
291458
private byte[] createRandomData()
292459
{
293460
final long seed = new Random().nextLong();

0 commit comments

Comments
 (0)