|
3 | 3 | EagerStoreReader, and ParallelStoreReader. |
4 | 4 | """ |
5 | 5 |
|
| 6 | +import pickle |
6 | 7 | from io import BytesIO |
7 | 8 |
|
8 | 9 | import pytest |
|
14 | 15 | ParallelStoreReader, |
15 | 16 | ) |
16 | 17 |
|
| 18 | +from .mocks import PicklableStore |
| 19 | + |
17 | 20 |
|
18 | 21 | ALL_READERS = [BufferedStoreReader, EagerStoreReader, ParallelStoreReader] |
19 | 22 |
|
@@ -386,3 +389,86 @@ def test_reader_seek_invalid_whence_raises(ReaderClass): |
386 | 389 |
|
387 | 390 | with pytest.raises(ValueError): |
388 | 391 | reader.seek(0, 3) |
| 392 | + |
| 393 | + |
| 394 | +# ============================================================================= |
| 395 | +# Pickling tests |
| 396 | +# ============================================================================= |
| 397 | + |
| 398 | + |
| 399 | +@pytest.mark.parametrize("ReaderClass", ALL_READERS) |
| 400 | +def test_reader_pickle_roundtrip(ReaderClass): |
| 401 | + """Reader can be pickled and unpickled.""" |
| 402 | + store = PicklableStore() |
| 403 | + store.put("test.txt", b"hello world") |
| 404 | + |
| 405 | + reader = ReaderClass(store, "test.txt") |
| 406 | + |
| 407 | + pickled = pickle.dumps(reader) |
| 408 | + restored = pickle.loads(pickled) |
| 409 | + |
| 410 | + assert isinstance(restored, ReaderClass) |
| 411 | + |
| 412 | + |
| 413 | +@pytest.mark.parametrize("ReaderClass", ALL_READERS) |
| 414 | +def test_reader_pickle_preserves_path(ReaderClass): |
| 415 | + """Unpickled reader preserves the file path.""" |
| 416 | + store = PicklableStore() |
| 417 | + store.put("test.txt", b"hello world") |
| 418 | + |
| 419 | + reader = ReaderClass(store, "test.txt") |
| 420 | + restored = pickle.loads(pickle.dumps(reader)) |
| 421 | + |
| 422 | + assert restored._path == "test.txt" |
| 423 | + |
| 424 | + |
| 425 | +@pytest.mark.parametrize("ReaderClass", ALL_READERS) |
| 426 | +def test_reader_pickle_restored_is_functional(ReaderClass): |
| 427 | + """Restored reader can read data.""" |
| 428 | + store = PicklableStore() |
| 429 | + store.put("test.txt", b"hello world") |
| 430 | + |
| 431 | + reader = ReaderClass(store, "test.txt") |
| 432 | + restored = pickle.loads(pickle.dumps(reader)) |
| 433 | + |
| 434 | + # Should be able to read data |
| 435 | + assert restored.read(5) == b"hello" |
| 436 | + assert restored.read(6) == b" world" |
| 437 | + |
| 438 | + |
| 439 | +@pytest.mark.parametrize("ReaderClass", ALL_READERS) |
| 440 | +def test_reader_pickle_preserves_position(ReaderClass): |
| 441 | + """Unpickled reader preserves the current position.""" |
| 442 | + store = PicklableStore() |
| 443 | + store.put("test.txt", b"hello world") |
| 444 | + |
| 445 | + reader = ReaderClass(store, "test.txt") |
| 446 | + reader.read(5) # Move position to 5 |
| 447 | + assert reader.tell() == 5 |
| 448 | + |
| 449 | + restored = pickle.loads(pickle.dumps(reader)) |
| 450 | + |
| 451 | + assert restored.tell() == 5 |
| 452 | + assert restored.read(6) == b" world" |
| 453 | + |
| 454 | + |
| 455 | +@pytest.mark.parametrize("ReaderClass", ALL_READERS) |
| 456 | +def test_reader_pickle_multiple_protocols(ReaderClass): |
| 457 | + """Pickling works with different pickle protocols. |
| 458 | +
|
| 459 | + Note: EagerStoreReader uses BytesIO which requires protocol >= 2. |
| 460 | + """ |
| 461 | + store = PicklableStore() |
| 462 | + store.put("test.txt", b"hello world") |
| 463 | + |
| 464 | + reader = ReaderClass(store, "test.txt") |
| 465 | + |
| 466 | + # BytesIO (used by EagerStoreReader) requires protocol >= 2 |
| 467 | + min_protocol = 2 if ReaderClass == EagerStoreReader else 0 |
| 468 | + |
| 469 | + for protocol in range(min_protocol, pickle.HIGHEST_PROTOCOL + 1): |
| 470 | + pickled = pickle.dumps(reader, protocol=protocol) |
| 471 | + restored = pickle.loads(pickled) |
| 472 | + |
| 473 | + restored.seek(0) |
| 474 | + assert restored.read() == b"hello world" |
0 commit comments