Skip to content

Commit af58fe2

Browse files
derek73claude
andcommitted
test: isolate global CONSTANTS collection mutations in conftest
The autouse fixture only snapshotted scalar config attrs, so tests that mutate a global collection (e.g. CONSTANTS.titles) relied on manual, non-exception-safe cleanup. If such a test failed before its cleanup ran, the mutation leaked into later tests — reintroducing the order-dependence the fixture was meant to eliminate. Snapshot and restore the collection managers (SetManager / TupleManager / RegexTupleManager) too, via a small type-aware clone (deepcopy can't round-trip RegexTupleManager's compiled patterns), and reset the lazy _pst cache so it recomputes from the restored collections. The manual cleanup in test_can_change_global_constants is now redundant and removed. Verified order-independence across randomized seeds and via an injected test that mutates a global collection then fails before cleanup: the following test still sees pristine config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b68ef39 commit af58fe2

2 files changed

Lines changed: 50 additions & 8 deletions

File tree

tests/conftest.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import pytest
44

5-
from nameparser.config import CONSTANTS
5+
from nameparser.config import CONSTANTS, SetManager, TupleManager
6+
7+
ConfigCollection = SetManager | TupleManager
68

79
# Scalar (non-collection) config attributes that individual tests mutate on the
810
# global CONSTANTS singleton. Several tests change these without restoring them;
@@ -19,6 +21,36 @@
1921
"force_mixed_case_capitalization",
2022
)
2123

24+
# Collection config attributes (the SetManager / TupleManager constants). Tests
25+
# that customize the global CONSTANTS — e.g. adding or removing a title — mutate
26+
# these in place, so a shallow snapshot of the reference would not protect later
27+
# tests. We snapshot independent copies and restore them, making collection
28+
# mutations order-independent too.
29+
_COLLECTION_CONFIG_ATTRS = (
30+
"prefixes",
31+
"suffix_acronyms",
32+
"suffix_not_acronyms",
33+
"titles",
34+
"first_name_titles",
35+
"conjunctions",
36+
"capitalization_exceptions",
37+
"regexes",
38+
)
39+
40+
41+
def _clone_config_collection(value: ConfigCollection) -> ConfigCollection:
42+
"""Return an independent copy of a config collection manager.
43+
44+
``copy.deepcopy`` is not used because ``RegexTupleManager`` carries compiled
45+
patterns that its ``__reduce__`` cannot round-trip. Rebuilding from the
46+
manager's own contents copies the container while sharing the (immutable)
47+
elements, which is all the snapshot needs.
48+
"""
49+
if isinstance(value, SetManager):
50+
return SetManager(set(value))
51+
# TupleManager / RegexTupleManager are dict subclasses.
52+
return type(value)(dict(value))
53+
2254

2355
@pytest.fixture(autouse=True, params=['', None], ids=['default', 'none'])
2456
def empty_attribute_default(request: pytest.FixtureRequest) -> Iterator[str | None]:
@@ -27,12 +59,22 @@ def empty_attribute_default(request: pytest.FixtureRequest) -> Iterator[str | No
2759
Reproduces the original tests.py __main__ block, which ran the whole suite
2860
twice — once with the default ('') and once with None — as a regression
2961
check that the three parsing code paths agree. The surrounding snapshot of
30-
the scalar CONSTANTS attributes restores any global config a test mutates,
31-
so tests do not leak state into one another (the original relied on
32-
unittest's alphabetical method ordering to mask such leaks).
62+
both the scalar and the collection CONSTANTS attributes restores any global
63+
config a test mutates, so tests do not leak state into one another (the
64+
original relied on unittest's alphabetical method ordering to mask such
65+
leaks).
3366
"""
34-
snapshot = {attr: getattr(CONSTANTS, attr) for attr in _SCALAR_CONFIG_ATTRS}
67+
scalar_snapshot = {attr: getattr(CONSTANTS, attr) for attr in _SCALAR_CONFIG_ATTRS}
68+
collection_snapshot = {
69+
attr: _clone_config_collection(getattr(CONSTANTS, attr))
70+
for attr in _COLLECTION_CONFIG_ATTRS
71+
}
3572
CONSTANTS.empty_attribute_default = request.param
3673
yield request.param
37-
for attr, value in snapshot.items():
74+
for attr, value in scalar_snapshot.items():
75+
setattr(CONSTANTS, attr, value)
76+
for attr, value in collection_snapshot.items():
3877
setattr(CONSTANTS, attr, value)
78+
# Invalidate the lazily-built suffixes/prefixes/titles cache so it is
79+
# recomputed from the restored collections rather than a mutated one.
80+
CONSTANTS._pst = None

tests/test_constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ def test_can_change_global_constants(self) -> None:
5252
self.assertEqual('hon' in hn2.C.titles, False)
5353
self.assertEqual(hn.has_own_config, False)
5454
self.assertEqual(hn2.has_own_config, False)
55-
# clean up so we don't mess up other tests
56-
hn.C.titles.add('hon')
55+
# No manual cleanup needed: the autouse fixture in conftest.py snapshots
56+
# and restores the global CONSTANTS collections around every test.
5757

5858
def test_remove_multiple_arguments(self) -> None:
5959
hn = HumanName("Ms Hon Solo", constants=None)

0 commit comments

Comments
 (0)