mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-15 17:25:26 -04:00
452a94fb1b
Test-only refactor continuing #2523. Centralizes the final repeated fake src.endpoint_resolver cleanup pattern into a focused import-state helper.
427 lines
17 KiB
Python
427 lines
17 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_fake_endpoint_resolver_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")
|
|
|
|
# Names touched by clear_fake_endpoint_resolver_modules — snapshot/restore these
|
|
# so the tests never leak into the real src/routes packages.
|
|
_RESOLVER_NAMES = (
|
|
"src",
|
|
"src.endpoint_resolver",
|
|
"routes",
|
|
"routes.model_routes",
|
|
"routes.chat_routes",
|
|
)
|
|
|
|
|
|
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
|
|
|
|
|
|
def test_clear_fake_resolver_removes_stub_endpoint_resolver():
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
fake_resolver = types.ModuleType("src.endpoint_resolver") # no __file__ => stub
|
|
fake_src.endpoint_resolver = fake_resolver
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = fake_resolver
|
|
|
|
clear_fake_endpoint_resolver_modules()
|
|
|
|
assert "src.endpoint_resolver" not in sys.modules
|
|
assert not hasattr(fake_src, "endpoint_resolver")
|
|
|
|
|
|
def test_clear_fake_resolver_preserves_real_endpoint_resolver():
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
real_resolver = types.ModuleType("src.endpoint_resolver")
|
|
real_resolver.__file__ = "/somewhere/src/endpoint_resolver.py" # looks on-disk
|
|
fake_src.endpoint_resolver = real_resolver
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = real_resolver
|
|
|
|
clear_fake_endpoint_resolver_modules()
|
|
|
|
assert sys.modules["src.endpoint_resolver"] is real_resolver
|
|
assert fake_src.endpoint_resolver is real_resolver
|
|
|
|
|
|
def test_clear_fake_resolver_evicts_empty_file_resolver():
|
|
"""A resolver with __file__ = "" is a stub under the old truthiness guard, so
|
|
it (and its dependents) must be evicted, not preserved."""
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
empty_resolver = types.ModuleType("src.endpoint_resolver")
|
|
empty_resolver.__file__ = "" # falsy => stub
|
|
fake_src.endpoint_resolver = empty_resolver
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = empty_resolver
|
|
model_routes = types.ModuleType("routes.model_routes")
|
|
sys.modules["routes.model_routes"] = model_routes
|
|
|
|
clear_fake_endpoint_resolver_modules()
|
|
|
|
assert "src.endpoint_resolver" not in sys.modules
|
|
assert not hasattr(fake_src, "endpoint_resolver")
|
|
assert "routes.model_routes" not in sys.modules
|
|
|
|
|
|
def test_clear_fake_resolver_removes_model_routes_when_resolver_fake():
|
|
"""model_routes is dropped, and its parent `routes` attr is cleared too —
|
|
the behavior delta over the old bare sys.modules.pop() guards."""
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
fake_resolver = types.ModuleType("src.endpoint_resolver")
|
|
fake_src.endpoint_resolver = fake_resolver
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = fake_resolver
|
|
|
|
fake_routes = types.ModuleType("routes")
|
|
model_routes = types.ModuleType("routes.model_routes")
|
|
fake_routes.model_routes = model_routes
|
|
sys.modules["routes"] = fake_routes
|
|
sys.modules["routes.model_routes"] = model_routes
|
|
|
|
clear_fake_endpoint_resolver_modules()
|
|
|
|
assert "routes.model_routes" not in sys.modules
|
|
assert not hasattr(fake_routes, "model_routes")
|
|
|
|
|
|
def test_clear_fake_resolver_removes_extra_modules_when_resolver_fake():
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
fake_resolver = types.ModuleType("src.endpoint_resolver")
|
|
fake_src.endpoint_resolver = fake_resolver
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = fake_resolver
|
|
|
|
fake_routes = types.ModuleType("routes")
|
|
chat_routes = types.ModuleType("routes.chat_routes")
|
|
fake_routes.chat_routes = chat_routes
|
|
sys.modules["routes"] = fake_routes
|
|
sys.modules["routes.chat_routes"] = chat_routes
|
|
|
|
clear_fake_endpoint_resolver_modules("routes.chat_routes")
|
|
|
|
assert "routes.chat_routes" not in sys.modules
|
|
assert not hasattr(fake_routes, "chat_routes")
|
|
|
|
|
|
def test_clear_fake_resolver_keeps_dependents_when_resolver_real():
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
real_resolver = types.ModuleType("src.endpoint_resolver")
|
|
real_resolver.__file__ = "/somewhere/src/endpoint_resolver.py"
|
|
fake_src.endpoint_resolver = real_resolver
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = real_resolver
|
|
|
|
model_routes = types.ModuleType("routes.model_routes")
|
|
chat_routes = types.ModuleType("routes.chat_routes")
|
|
sys.modules["routes.model_routes"] = model_routes
|
|
sys.modules["routes.chat_routes"] = chat_routes
|
|
|
|
clear_fake_endpoint_resolver_modules("routes.chat_routes")
|
|
|
|
assert sys.modules["routes.model_routes"] is model_routes
|
|
assert sys.modules["routes.chat_routes"] is chat_routes
|
|
|
|
|
|
def test_clear_fake_resolver_noop_when_nothing_cached():
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
sys.modules.pop("src.endpoint_resolver", None)
|
|
fake_src = types.ModuleType("src") # no endpoint_resolver attr
|
|
sys.modules["src"] = fake_src
|
|
model_routes = types.ModuleType("routes.model_routes")
|
|
sys.modules["routes.model_routes"] = model_routes
|
|
|
|
clear_fake_endpoint_resolver_modules() # must not raise
|
|
|
|
assert "src.endpoint_resolver" not in sys.modules
|
|
# dependents are left alone when the resolver was never cached
|
|
assert sys.modules["routes.model_routes"] is model_routes
|
|
|
|
|
|
def test_clear_fake_resolver_keeps_parent_attr_pointing_elsewhere():
|
|
"""When the cached src.endpoint_resolver is a stub but the `endpoint_resolver`
|
|
attr on the src package points at a *different* object, the attr is left
|
|
intact — only the same fake object is unlinked."""
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
fake_src = types.ModuleType("src")
|
|
cached_fake = types.ModuleType("src.endpoint_resolver") # the stub in sys.modules
|
|
other = types.ModuleType("src.endpoint_resolver") # parent attr points here
|
|
fake_src.endpoint_resolver = other
|
|
sys.modules["src"] = fake_src
|
|
sys.modules["src.endpoint_resolver"] = cached_fake
|
|
|
|
clear_fake_endpoint_resolver_modules()
|
|
|
|
assert "src.endpoint_resolver" not in sys.modules
|
|
assert fake_src.endpoint_resolver is other
|
|
|
|
|
|
def test_clear_fake_resolver_uses_parent_attr_when_not_in_sys_modules():
|
|
"""A stub reachable only via the src package's `endpoint_resolver` attribute
|
|
(not in sys.modules) is still detected, unlinked, and triggers dependent
|
|
eviction."""
|
|
with preserve_import_state(*_RESOLVER_NAMES):
|
|
sys.modules.pop("src.endpoint_resolver", None)
|
|
fake_src = types.ModuleType("src")
|
|
fake_resolver = types.ModuleType("src.endpoint_resolver")
|
|
fake_src.endpoint_resolver = fake_resolver
|
|
sys.modules["src"] = fake_src
|
|
model_routes = types.ModuleType("routes.model_routes")
|
|
sys.modules["routes.model_routes"] = model_routes
|
|
|
|
clear_fake_endpoint_resolver_modules()
|
|
|
|
assert not hasattr(fake_src, "endpoint_resolver")
|
|
assert "routes.model_routes" not in sys.modules
|