Skip to content

Commit 3eb82a4

Browse files
committed
♻️ Refactor pickle tests
The main goal of these changes is to make test_pickle.py more readable and eliminate get_pickles.py. These changes: - add dict_data, pickled_data, and pickle_file_path fixtures - rename test_pickle to test_unpickle - add test_pickle_format_stability - replace test_load_from_file with test_pickle_backward_compatibility - replace get_pickles.py with test_write_pickle_file
1 parent 770445a commit 3eb82a4

File tree

7 files changed

+77
-64
lines changed

7 files changed

+77
-64
lines changed

.mypy.ini

-6
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@ disable_error_code =
5353
type-arg,
5454
var-annotated,
5555

56-
[mypy-gen_pickles]
57-
disable_error_code =
58-
attr-defined,
59-
no-untyped-call,
60-
no-untyped-def,
61-
6256
[mypy-test_abc]
6357
disable_error_code =
6458
no-untyped-call,

CHANGES/938.contrib.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Pickle tests have been refactored in order
2+
to make ``test_pickle.py`` more readable
3+
and eliminate ``get_pickles.py``.

docs/conf.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import re
2020
from contextlib import suppress
2121
from pathlib import Path
22+
from typing import Optional
2223

2324
import alabaster
2425
from sphinx.addnodes import pending_xref
@@ -384,7 +385,7 @@ def _replace_missing_aiohttp_hdrs_reference(
384385
env: BuildEnvironment,
385386
node: pending_xref,
386387
contnode: literal,
387-
) -> reference:
388+
) -> Optional[reference]:
388389
if (node.get('refdomain'), node.get('reftype')) != ("py", "mod"):
389390
return None
390391

tests/conftest.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import pickle
55
from dataclasses import dataclass
66
from importlib import import_module
7+
from pathlib import Path
78
from sys import version_info as _version_info
89
from types import ModuleType
9-
from typing import Callable, Type
10+
from typing import Any, Callable, Type
1011

1112
try:
1213
from functools import cached_property # Python 3.8+
@@ -23,6 +24,7 @@ def cached_property(func):
2324

2425
C_EXT_MARK = pytest.mark.c_extension
2526
PY_38_AND_BELOW = _version_info < (3, 9)
27+
TESTS_DIR = Path(__file__).parent.resolve()
2628

2729

2830
@dataclass(frozen=True)
@@ -139,15 +141,15 @@ def any_multidict_proxy_class(
139141
@pytest.fixture(scope="session")
140142
def case_sensitive_multidict_proxy_class(
141143
multidict_module: ModuleType,
142-
) -> Type[MutableMultiMapping[str]]:
144+
) -> Type[MultiMapping[str]]:
143145
"""Return a case-sensitive immutable multidict class."""
144146
return multidict_module.MultiDictProxy
145147

146148

147149
@pytest.fixture(scope="session")
148150
def case_insensitive_multidict_proxy_class(
149151
multidict_module: ModuleType,
150-
) -> Type[MutableMultiMapping[str]]:
152+
) -> Type[MultiMapping[str]]:
151153
"""Return a case-insensitive immutable multidict class."""
152154
return multidict_module.CIMultiDictProxy
153155

@@ -158,6 +160,34 @@ def multidict_getversion_callable(multidict_module: ModuleType) -> Callable:
158160
return multidict_module.getversion
159161

160162

163+
@pytest.fixture
164+
def dict_data() -> Any:
165+
return [("a", 1), ("a", 2)]
166+
167+
168+
@pytest.fixture
169+
def pickled_data(
170+
any_multidict_class,
171+
pickle_protocol: int,
172+
dict_data: Any,
173+
) -> bytes:
174+
"""Generates a pickled representation of the test data"""
175+
d = any_multidict_class(dict_data)
176+
return pickle.dumps(d, pickle_protocol)
177+
178+
179+
@pytest.fixture
180+
def pickle_file_path(
181+
any_multidict_class_name: str,
182+
multidict_implementation: MultidictImplementation,
183+
pickle_protocol: int,
184+
) -> Path:
185+
return TESTS_DIR / (
186+
f"{any_multidict_class_name.lower()}-{multidict_implementation.tag}"
187+
f".pickle.{pickle_protocol}"
188+
)
189+
190+
161191
def pytest_addoption(
162192
parser: pytest.Parser,
163193
pluginmanager: pytest.PytestPluginManager,

tests/gen_pickles.py

-28
This file was deleted.

tests/test_multidict.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from collections.abc import Mapping
99
from types import ModuleType
1010
from typing import (
11+
Any,
1112
Callable,
1213
Dict,
1314
Iterable,
@@ -574,8 +575,8 @@ def test_preserve_stable_ordering(
574575

575576
assert s == "a=1&b=2&a=3"
576577

577-
def test_get(self, cls: Type[MultiDict[int]]) -> None:
578-
d = cls([("a", 1), ("a", 2)])
578+
def test_get(self, cls: Type[MultiDict[int]], dict_data: Any) -> None:
579+
d = cls(dict_data)
579580
assert d["a"] == 1
580581

581582
def test_items__repr__(self, cls: Type[MultiDict[str]]) -> None:

tests/test_pickle.py

+36-24
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,50 @@
1+
import os
12
import pickle
23
from pathlib import Path
34

45
import pytest
56

6-
here = Path(__file__).resolve().parent
7+
WRITE_PICKLE_FILES = bool(os.environ.get("WRITE_PICKLE_FILES"))
78

89

9-
def test_pickle(any_multidict_class, pickle_protocol):
10-
d = any_multidict_class([("a", 1), ("a", 2)])
11-
pbytes = pickle.dumps(d, pickle_protocol)
12-
obj = pickle.loads(pbytes)
13-
assert d == obj
14-
assert isinstance(obj, any_multidict_class)
10+
def test_unpickle(any_multidict_class, dict_data, pickled_data):
11+
expected = any_multidict_class(dict_data)
12+
actual = pickle.loads(pickled_data)
13+
assert actual == expected
14+
assert isinstance(actual, any_multidict_class)
1515

1616

17-
def test_pickle_proxy(any_multidict_class, any_multidict_proxy_class):
18-
d = any_multidict_class([("a", 1), ("a", 2)])
17+
def test_pickle_proxy(any_multidict_class, any_multidict_proxy_class, dict_data):
18+
d = any_multidict_class(dict_data)
1919
proxy = any_multidict_proxy_class(d)
2020
with pytest.raises(TypeError):
2121
pickle.dumps(proxy)
2222

2323

24-
def test_load_from_file(any_multidict_class, multidict_implementation, pickle_protocol):
25-
multidict_class_name = any_multidict_class.__name__
26-
pickle_file_basename = "-".join(
27-
(
28-
multidict_class_name.lower(),
29-
multidict_implementation.tag,
30-
)
31-
)
32-
d = any_multidict_class([("a", 1), ("a", 2)])
33-
fname = f"{pickle_file_basename}.pickle.{pickle_protocol}"
34-
p = here / fname
35-
with p.open("rb") as f:
36-
obj = pickle.load(f)
37-
assert d == obj
38-
assert isinstance(obj, any_multidict_class)
24+
def test_pickle_format_stability(pickled_data, pickle_file_path, pickle_protocol):
25+
if pickle_protocol == 0:
26+
# TODO: consider updating pickle files
27+
pytest.skip(reason="Format for pickle protocol 0 is changed, it's a known fact")
28+
expected = pickle_file_path.read_bytes()
29+
assert pickled_data == expected
30+
31+
32+
def test_pickle_backward_compatibility(
33+
any_multidict_class,
34+
dict_data,
35+
pickle_file_path,
36+
):
37+
expected = any_multidict_class(dict_data)
38+
with pickle_file_path.open("rb") as f:
39+
actual = pickle.load(f)
40+
41+
assert actual == expected
42+
assert isinstance(actual, any_multidict_class)
43+
44+
45+
@pytest.mark.skipif(
46+
not WRITE_PICKLE_FILES,
47+
reason="This is a helper that writes pickle test files",
48+
)
49+
def test_write_pickle_file(pickled_data: bytes, pickle_file_path: Path) -> None:
50+
pickle_file_path.write_bytes(pickled_data)

0 commit comments

Comments
 (0)