|
4 | 4 | import org.junit.jupiter.api.Nested; |
5 | 5 | import org.junit.jupiter.api.Test; |
6 | 6 | import org.junit.jupiter.params.ParameterizedTest; |
| 7 | +import org.junit.jupiter.params.provider.Arguments; |
| 8 | +import org.junit.jupiter.params.provider.MethodSource; |
7 | 9 | import org.junit.jupiter.params.provider.ValueSource; |
8 | 10 |
|
9 | 11 | import java.io.ByteArrayInputStream; |
|
14 | 16 | import java.lang.foreign.MemorySegment; |
15 | 17 | import java.nio.ByteBuffer; |
16 | 18 | import java.nio.charset.StandardCharsets; |
| 19 | +import java.util.stream.Stream; |
17 | 20 |
|
18 | 21 | import static io.github.dfa1.zstd.ZstdTestSupport.randomBytes; |
19 | 22 | import static io.github.dfa1.zstd.ZstdTestSupport.sample; |
@@ -290,35 +293,20 @@ void closeFlushesAndClosesTheUnderlyingStreamExactlyOnce() throws IOException { |
290 | 293 | assertThat(streamDecompress(sink.toByteArray())).isEqualTo(original); |
291 | 294 | } |
292 | 295 |
|
293 | | - @Test |
294 | | - void writeAfterCloseThrows() throws IOException { |
| 296 | + @ParameterizedTest(name = "{0}") |
| 297 | + @MethodSource("io.github.dfa1.zstd.ZstdStreamTest#closedStreamOperations") |
| 298 | + void operationAfterCloseThrows(String name, ClosedStreamOperation operation) throws IOException { |
295 | 299 | // Given a closed stream |
296 | | - ZstdOutputStream zout = new ZstdOutputStream(new ByteArrayOutputStream()); |
297 | | - zout.close(); |
| 300 | + ThrowingCallable closedStreamOperation = operation.closeThenOperate(); |
298 | 301 |
|
299 | | - // When written to |
300 | | - ThrowingCallable result = () -> zout.write(1); |
| 302 | + // When operating on it |
| 303 | + ThrowingCallable result = closedStreamOperation; |
301 | 304 |
|
302 | 305 | // Then it refuses with an IOException rather than touching freed native state |
303 | 306 | assertThatThrownBy(result) |
304 | 307 | .isInstanceOf(IOException.class) |
305 | 308 | .hasMessageContaining("closed"); |
306 | 309 | } |
307 | | - |
308 | | - @Test |
309 | | - void flushAfterCloseThrows() throws IOException { |
310 | | - // Given a closed stream |
311 | | - ZstdOutputStream zout = new ZstdOutputStream(new ByteArrayOutputStream()); |
312 | | - zout.close(); |
313 | | - |
314 | | - // When flushed |
315 | | - ThrowingCallable result = zout::flush; |
316 | | - |
317 | | - // Then it refuses with an IOException |
318 | | - assertThatThrownBy(result) |
319 | | - .isInstanceOf(IOException.class) |
320 | | - .hasMessageContaining("closed"); |
321 | | - } |
322 | 310 | } |
323 | 311 |
|
324 | 312 | @Nested |
@@ -357,22 +345,6 @@ void readPastEndOfStreamStaysMinusOne() throws IOException { |
357 | 345 | } |
358 | 346 | } |
359 | 347 |
|
360 | | - @Test |
361 | | - void readAfterCloseThrows() throws IOException { |
362 | | - // Given a closed input stream |
363 | | - byte[] frame = streamCompress("payload".getBytes(StandardCharsets.UTF_8), 3); |
364 | | - ZstdInputStream zin = new ZstdInputStream(new ByteArrayInputStream(frame)); |
365 | | - zin.close(); |
366 | | - |
367 | | - // When read |
368 | | - ThrowingCallable result = zin::read; |
369 | | - |
370 | | - // Then it refuses with an IOException rather than touching freed native state |
371 | | - assertThatThrownBy(result) |
372 | | - .isInstanceOf(IOException.class) |
373 | | - .hasMessageContaining("closed"); |
374 | | - } |
375 | | - |
376 | 348 | @Test |
377 | 349 | void closeClosesTheUnderlyingStreamExactlyOnce() throws IOException { |
378 | 350 | // Given an input stream over a source that tracks close() |
@@ -422,6 +394,32 @@ void readsCorrectlyWhenInputArrivesOneByteAtATime(int size) throws IOException { |
422 | 394 | } |
423 | 395 | } |
424 | 396 |
|
| 397 | + private static Stream<Arguments> closedStreamOperations() { |
| 398 | + return Stream.of( |
| 399 | + Arguments.of("write after close", (ClosedStreamOperation) () -> { |
| 400 | + ZstdOutputStream stream = new ZstdOutputStream(new ByteArrayOutputStream()); |
| 401 | + stream.close(); |
| 402 | + return () -> stream.write(1); |
| 403 | + }), |
| 404 | + Arguments.of("flush after close", (ClosedStreamOperation) () -> { |
| 405 | + ZstdOutputStream stream = new ZstdOutputStream(new ByteArrayOutputStream()); |
| 406 | + stream.close(); |
| 407 | + return stream::flush; |
| 408 | + }), |
| 409 | + Arguments.of("read after close", (ClosedStreamOperation) () -> { |
| 410 | + byte[] frame = streamCompress("payload".getBytes(StandardCharsets.UTF_8), 3); |
| 411 | + ZstdInputStream stream = new ZstdInputStream(new ByteArrayInputStream(frame)); |
| 412 | + stream.close(); |
| 413 | + return stream::read; |
| 414 | + }) |
| 415 | + ); |
| 416 | + } |
| 417 | + |
| 418 | + @FunctionalInterface |
| 419 | + private interface ClosedStreamOperation { |
| 420 | + ThrowingCallable closeThenOperate() throws IOException; |
| 421 | + } |
| 422 | + |
425 | 423 | /// A sink that records flush/close calls while retaining the bytes written to it |
426 | 424 | /// (a [ByteArrayOutputStream] whose close is a no-op, so bytes stay readable). |
427 | 425 | private static final class CountingOutputStream extends ByteArrayOutputStream { |
|
0 commit comments