|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -import pickle # ruff:ignore[suspicious-pickle-import] # round-trips Timeout to assert it pickles |
4 | | -from typing import TYPE_CHECKING |
| 3 | +import copy |
| 4 | +import pickle # ruff:ignore[suspicious-pickle-import] # round-trips exceptions created in these tests |
| 5 | +from functools import partial |
| 6 | +from typing import TYPE_CHECKING, Final |
5 | 7 |
|
6 | 8 | import pytest |
7 | 9 |
|
8 | | -from filelock import Timeout |
| 10 | +from filelock import SoftFileLockProtocolError, Timeout |
9 | 11 |
|
10 | 12 | if TYPE_CHECKING: |
11 | 13 | from collections.abc import Callable |
|
17 | 19 | pytest.param(str, "The file lock '/path/to/lock' could not be acquired.", id="str"), |
18 | 20 | pytest.param(repr, "Timeout('/path/to/lock')", id="repr"), |
19 | 21 | pytest.param(lambda t: t.lock_file, "/path/to/lock", id="lock_file"), |
20 | | - pytest.param(lambda t: t.__reduce__(), (Timeout, ("/path/to/lock",)), id="reduce"), |
21 | 22 | ], |
22 | 23 | ) |
23 | | -def test_timeout_attribute(timeout: Timeout, extract: Callable[[Timeout], object], expected: object) -> None: |
24 | | - assert extract(timeout) == expected |
| 24 | +def test_timeout_attribute(extract: Callable[[Timeout], str], expected: str) -> None: |
| 25 | + assert extract(Timeout("/path/to/lock")) == expected |
25 | 26 |
|
26 | 27 |
|
27 | | -def test_timeout_pickle(timeout: Timeout) -> None: |
28 | | - reloaded = pickle.loads(pickle.dumps(timeout)) # ruff:ignore[suspicious-pickle-usage] # input is the Timeout built in this test |
29 | | - assert (type(reloaded), str(reloaded), repr(reloaded), reloaded.lock_file) == ( |
30 | | - type(timeout), |
31 | | - str(timeout), |
32 | | - repr(timeout), |
33 | | - timeout.lock_file, |
| 28 | +def test_exception_serialization_preserves_diagnostics( |
| 29 | + error: Timeout | SoftFileLockProtocolError, |
| 30 | + clone: Callable[[Timeout | SoftFileLockProtocolError], Timeout | SoftFileLockProtocolError], |
| 31 | +) -> None: |
| 32 | + # Python 3.10 supports the notes attribute but has no add_note(). |
| 33 | + error.__notes__ = ["while acquiring the build cache"] |
| 34 | + vars(error)["request_id"] = "request-1" |
| 35 | + restored: Final = clone(error) |
| 36 | + assert ( |
| 37 | + type(restored), |
| 38 | + str(restored), |
| 39 | + repr(restored), |
| 40 | + restored.args, |
| 41 | + restored.lock_file, |
| 42 | + restored.__notes__, |
| 43 | + vars(restored)["request_id"], |
| 44 | + ) == ( |
| 45 | + type(error), |
| 46 | + str(error), |
| 47 | + repr(error), |
| 48 | + error.args, |
| 49 | + error.lock_file, |
| 50 | + ["while acquiring the build cache"], |
| 51 | + "request-1", |
34 | 52 | ) |
35 | 53 |
|
36 | 54 |
|
37 | | -@pytest.fixture |
38 | | -def timeout() -> Timeout: |
39 | | - return Timeout("/path/to/lock") |
| 55 | +def test_exception_serialization_creates_distinct_instance( |
| 56 | + error: Timeout | SoftFileLockProtocolError, |
| 57 | + clone: Callable[[Timeout | SoftFileLockProtocolError], Timeout | SoftFileLockProtocolError], |
| 58 | +) -> None: |
| 59 | + assert clone(error) is not error |
| 60 | + |
| 61 | + |
| 62 | +def test_exception_serialization_preserves_note_alias( |
| 63 | + error: Timeout | SoftFileLockProtocolError, |
| 64 | + clone: Callable[[Timeout | SoftFileLockProtocolError], Timeout | SoftFileLockProtocolError], |
| 65 | +) -> None: |
| 66 | + error.__notes__ = ["original"] |
| 67 | + vars(error)["diagnostics"] = error.__notes__ |
| 68 | + restored: Final = clone(error) |
| 69 | + assert vars(restored)["diagnostics"] is restored.__notes__ |
| 70 | + |
| 71 | + |
| 72 | +def test_exception_copy_shares_notes(error: Timeout | SoftFileLockProtocolError) -> None: |
| 73 | + error.__notes__ = ["original"] |
| 74 | + copy.copy(error).__notes__.append("copy") |
| 75 | + assert error.__notes__ == ["original", "copy"] |
| 76 | + |
| 77 | + |
| 78 | +def test_exception_deepcopy_isolates_notes(error: Timeout | SoftFileLockProtocolError) -> None: |
| 79 | + error.__notes__ = ["original"] |
| 80 | + copy.deepcopy(error).__notes__.append("copy") |
| 81 | + assert error.__notes__ == ["original"] |
| 82 | + |
| 83 | + |
| 84 | +def test_exception_serialization_preserves_cycle( |
| 85 | + error: Timeout | SoftFileLockProtocolError, |
| 86 | + clone: Callable[[Timeout | SoftFileLockProtocolError], Timeout | SoftFileLockProtocolError], |
| 87 | +) -> None: |
| 88 | + vars(error)["related"] = error |
| 89 | + restored: Final = clone(error) |
| 90 | + assert vars(restored)["related"] is (error if clone is copy.copy else restored) |
| 91 | + |
| 92 | + |
| 93 | +def test_protocol_error_serialization_preserves_claim( |
| 94 | + clone: Callable[[Timeout | SoftFileLockProtocolError], Timeout | SoftFileLockProtocolError], |
| 95 | +) -> None: |
| 96 | + restored: Final = clone(SoftFileLockProtocolError("/lock", "claim", "invalid marker")) |
| 97 | + assert isinstance(restored, SoftFileLockProtocolError) |
| 98 | + assert (restored.claim_name, restored.reason) == ("claim", "invalid marker") |
| 99 | + |
| 100 | + |
| 101 | +@pytest.fixture( |
| 102 | + params=[pytest.param("timeout", id="timeout"), pytest.param("claim", id="claim"), pytest.param(None, id="no-claim")] |
| 103 | +) |
| 104 | +def error(request: pytest.FixtureRequest) -> Timeout | SoftFileLockProtocolError: |
| 105 | + if request.param == "timeout": |
| 106 | + return Timeout("/path/to/lock") |
| 107 | + return SoftFileLockProtocolError("/path/to/lock", request.param, "invalid marker") |
| 108 | + |
| 109 | + |
| 110 | +def _pickle_round_trip( |
| 111 | + error: Timeout | SoftFileLockProtocolError, *, protocol: int |
| 112 | +) -> Timeout | SoftFileLockProtocolError: |
| 113 | + return pickle.loads(pickle.dumps(error, protocol=protocol)) # ruff:ignore[suspicious-pickle-usage] # input is created by these tests |
| 114 | + |
| 115 | + |
| 116 | +@pytest.fixture( |
| 117 | + params=[pytest.param(copy.copy, id="copy"), pytest.param(copy.deepcopy, id="deepcopy")] |
| 118 | + + [ |
| 119 | + pytest.param(partial(_pickle_round_trip, protocol=p), id=f"pickle-{p}") |
| 120 | + for p in range(pickle.HIGHEST_PROTOCOL + 1) |
| 121 | + ], |
| 122 | +) |
| 123 | +def clone( |
| 124 | + request: pytest.FixtureRequest, |
| 125 | +) -> Callable[[Timeout | SoftFileLockProtocolError], Timeout | SoftFileLockProtocolError]: |
| 126 | + return request.param |
0 commit comments