Files
odysseus/tests/test_helpers_import_state.py
T
Alexandre Teixeira 301d1109b5 refactor(tests): centralize fake database import-state cleanup
Test-only refactor continuing #2523. Centralizes the repeated guarded fake core.database/src.database import-state cleanup into a focused helper.
2026-06-05 12:27:44 +01:00

253 lines
9.5 KiB
Python

"""Focused tests for tests/helpers/import_state.py."""
import sys
import types
import pytest
from tests.helpers.import_state import (
clear_fake_database_modules,
clear_module,
preserve_import_state,
)
_SENTINEL = "tests._import_state_test_sentinel"
# Names touched by clear_fake_database_modules — snapshot/restore these so the
# tests never leak into the real core/src packages.
_DB_NAMES = ("core", "core.database", "src", "src.database")
def test_absent_module_is_removed_after_block():
assert _SENTINEL not in sys.modules
with preserve_import_state(_SENTINEL):
sys.modules[_SENTINEL] = types.ModuleType(_SENTINEL)
assert _SENTINEL not in sys.modules
def test_present_module_is_restored_after_block():
original = types.ModuleType(_SENTINEL)
sys.modules[_SENTINEL] = original
try:
with preserve_import_state(_SENTINEL):
sys.modules[_SENTINEL] = types.ModuleType(_SENTINEL)
assert sys.modules[_SENTINEL] is original
finally:
sys.modules.pop(_SENTINEL, None)
def test_parent_attr_restored_when_present_before_block():
fake_parent = types.ModuleType("_fake_istate_parent")
fake_child = types.ModuleType("_fake_istate_parent.child")
fake_parent.child = fake_child
sys.modules["_fake_istate_parent"] = fake_parent
sys.modules["_fake_istate_parent.child"] = fake_child
try:
with preserve_import_state("_fake_istate_parent.child"):
replacement = types.ModuleType("_fake_istate_parent.child")
sys.modules["_fake_istate_parent.child"] = replacement
fake_parent.child = replacement
assert sys.modules["_fake_istate_parent.child"] is fake_child
assert fake_parent.child is fake_child
finally:
sys.modules.pop("_fake_istate_parent", None)
sys.modules.pop("_fake_istate_parent.child", None)
def test_parent_attr_removed_when_absent_before_block():
fake_parent = types.ModuleType("_fake_istate_parent")
sys.modules["_fake_istate_parent"] = fake_parent
try:
with preserve_import_state("_fake_istate_parent.child"):
fake_child = types.ModuleType("_fake_istate_parent.child")
sys.modules["_fake_istate_parent.child"] = fake_child
fake_parent.child = fake_child
assert "_fake_istate_parent.child" not in sys.modules
assert not hasattr(fake_parent, "child")
finally:
sys.modules.pop("_fake_istate_parent", None)
sys.modules.pop("_fake_istate_parent.child", None)
def test_state_restored_on_exception():
assert _SENTINEL not in sys.modules
with pytest.raises(RuntimeError, match="expected"):
with preserve_import_state(_SENTINEL):
sys.modules[_SENTINEL] = types.ModuleType(_SENTINEL)
raise RuntimeError("expected")
assert _SENTINEL not in sys.modules
def test_multiple_modules_all_restored():
names = [f"tests._istate_multi_{i}" for i in range(3)]
for n in names:
assert n not in sys.modules
with preserve_import_state(*names):
for n in names:
sys.modules[n] = types.ModuleType(n)
for n in names:
assert n not in sys.modules
def test_clear_module_removes_entry():
sys.modules[_SENTINEL] = types.ModuleType(_SENTINEL)
try:
clear_module(_SENTINEL)
assert _SENTINEL not in sys.modules
finally:
sys.modules.pop(_SENTINEL, None)
def test_clear_module_removes_parent_attr():
fake_parent = types.ModuleType("_fake_istate_parent")
fake_child = types.ModuleType("_fake_istate_parent.child")
fake_parent.child = fake_child
sys.modules["_fake_istate_parent"] = fake_parent
sys.modules["_fake_istate_parent.child"] = fake_child
try:
clear_module("_fake_istate_parent.child")
assert "_fake_istate_parent.child" not in sys.modules
assert not hasattr(fake_parent, "child")
finally:
sys.modules.pop("_fake_istate_parent", None)
sys.modules.pop("_fake_istate_parent.child", None)
def test_clear_module_tolerates_absent_entry():
assert _SENTINEL not in sys.modules
clear_module(_SENTINEL) # must not raise
def test_parent_attr_restored_correctly_when_parent_also_preserved():
"""When a parent package and its child are both named, the child's
parent-attr restore must target the *saved* parent module, not the mutated
one. This requires phase 1 (sys.modules) to complete before phase 2 (attrs).
Tested with child listed before parent to trigger the failure path in a
naive single-pass implementation.
"""
fake_parent = types.ModuleType("_fake_istate_parent")
fake_child = types.ModuleType("_fake_istate_parent.child")
fake_parent.child = fake_child
sys.modules["_fake_istate_parent"] = fake_parent
sys.modules["_fake_istate_parent.child"] = fake_child
try:
# child before parent: old single-pass restore would write the child attr
# onto the still-mutated parent, then replace sys.modules["_fake_istate_parent"]
# — leaving fake_parent.child untouched.
with preserve_import_state("_fake_istate_parent.child", "_fake_istate_parent"):
new_parent = types.ModuleType("_fake_istate_parent")
new_child = types.ModuleType("_fake_istate_parent.child")
new_parent.child = new_child
sys.modules["_fake_istate_parent"] = new_parent
sys.modules["_fake_istate_parent.child"] = new_child
# sys.modules entries restored
assert sys.modules["_fake_istate_parent"] is fake_parent
assert sys.modules["_fake_istate_parent.child"] is fake_child
# parent-attr written onto the restored (saved) parent, not the mutated one
assert fake_parent.child is fake_child
finally:
sys.modules.pop("_fake_istate_parent", None)
sys.modules.pop("_fake_istate_parent.child", None)
def test_clear_fake_database_removes_stub_core_database():
with preserve_import_state(*_DB_NAMES):
fake_core = types.ModuleType("core")
fake_db = types.ModuleType("core.database") # no __file__ => a stub
fake_core.database = fake_db
sys.modules["core"] = fake_core
sys.modules["core.database"] = fake_db
clear_fake_database_modules()
assert "core.database" not in sys.modules
assert not hasattr(fake_core, "database")
def test_clear_fake_database_preserves_real_core_database():
with preserve_import_state(*_DB_NAMES):
fake_core = types.ModuleType("core")
real_db = types.ModuleType("core.database")
real_db.__file__ = "/somewhere/core/database.py" # looks on-disk
fake_core.database = real_db
sys.modules["core"] = fake_core
sys.modules["core.database"] = real_db
clear_fake_database_modules()
assert sys.modules["core.database"] is real_db
assert fake_core.database is real_db
def test_clear_fake_database_drops_src_database_when_core_is_fake():
with preserve_import_state(*_DB_NAMES):
fake_core = types.ModuleType("core")
fake_db = types.ModuleType("core.database")
fake_core.database = fake_db
sys.modules["core"] = fake_core
sys.modules["core.database"] = fake_db
sys.modules["src.database"] = types.ModuleType("src.database")
clear_fake_database_modules()
assert "src.database" not in sys.modules
def test_clear_fake_database_leaves_src_database_when_core_is_real():
with preserve_import_state(*_DB_NAMES):
fake_core = types.ModuleType("core")
real_db = types.ModuleType("core.database")
real_db.__file__ = "/somewhere/core/database.py"
fake_core.database = real_db
sys.modules["core"] = fake_core
sys.modules["core.database"] = real_db
src_db = types.ModuleType("src.database")
sys.modules["src.database"] = src_db
clear_fake_database_modules()
assert sys.modules["src.database"] is src_db
def test_clear_fake_database_keeps_parent_attr_pointing_elsewhere():
"""When the cached core.database is a stub but the `database` attr on the
core package points at a *different* object, the attr is left intact —
only the same fake object is unlinked."""
with preserve_import_state(*_DB_NAMES):
fake_core = types.ModuleType("core")
cached_fake = types.ModuleType("core.database") # the stub in sys.modules
other = types.ModuleType("core.database") # parent attr points here
fake_core.database = other
sys.modules["core"] = fake_core
sys.modules["core.database"] = cached_fake
clear_fake_database_modules()
assert "core.database" not in sys.modules
assert fake_core.database is other
def test_clear_fake_database_uses_parent_attr_when_not_in_sys_modules():
"""A stub reachable only via the core package's `database` attribute (not in
sys.modules) is still detected and unlinked from the parent."""
with preserve_import_state(*_DB_NAMES):
sys.modules.pop("core.database", None)
fake_core = types.ModuleType("core")
fake_db = types.ModuleType("core.database")
fake_core.database = fake_db
sys.modules["core"] = fake_core
clear_fake_database_modules()
assert not hasattr(fake_core, "database")
def test_clear_fake_database_noop_when_nothing_cached():
with preserve_import_state(*_DB_NAMES):
sys.modules.pop("core.database", None)
fake_core = types.ModuleType("core") # no `database` attr
sys.modules["core"] = fake_core
clear_fake_database_modules() # must not raise
assert "core.database" not in sys.modules