A subset of test files in an order the suite was not written for failed 3 test_init.py::TestImportStep tests with ModuleNotFoundError: voluptuous, while the full suite stayed green. Two order-only mechanisms, both now fixed at the source: 1. entity_stublib.install() guarded on ONE sentinel (`homeassistant.components.media_player`). A file whose teardown popped `voluptuous` but left the sentinel turned the next file's install() into a no-op, so importing config_flow failed. The guard now checks every face a file scopes on (`_STUB_FACES`, derived from ENTITIES_STUB_NAMES so the two cannot drift) and rebuilds the set when anything is missing. install() also registers the `homeassistant.helpers.selector` face itself, which the per-file installers had been adding separately — without it the derived list would never be complete. 2. Teardowns restored only the names their file had listed, so a module the file loaded (`nsh.diagnostics`, `nsh.media_player`) survived into the next file still holding stub objects — a stub exception class, the redact helper — that had since been replaced; comparisons against the new objects then failed (5 tests in test_command_routing on an unlucky order). Every file now ends through entity_stublib.snapshot_modules()/restore_modules(), which drops EVERYTHING the file created under the harness namespaces (homeassistant/voluptuous/nsh) and puts back everything it replaced. Hand-written lists were the bug, so there is none to keep in sync. The t7 no-leak discipline is unchanged: a file's stubs still never reach another file, and a file's loaded modules still do not survive it. A session-scoped baseline install was tried first and rejected: files whose own installer guards on "the stubs are already there" skipped installing their richer faces, which produced exactly the inconsistent state a baseline is meant to prevent (10 test_standalone_setup failures). tests/test_harness_scoping.py: the sentinel poisoning, the wholesale-rebuild invariant, a teardown that removes what the file loaded, the stale-object case (diagnostics vs the redact helper), and — because order is the thing under test — two subprocess runs of the reported orderings. Verified: the reported order (test_command_routing, test_protocol_transport, test_standalone_setup, test_init) 152 passed / 1 skipped; the minimal pair both ways; the previously failing 6-test order now 167 passed; every test file in REVERSE alphabetical order 369 passed / 2 skipped; two scrambled thirds green; full suite 369 / 2 and 370 / 1 with androidtvremote2; ruff clean.
54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
"""Test bootstrap.
|
|
|
|
Puts the vendored ha_adb_common package on sys.path so helpers can be imported
|
|
directly (their code is stdlib-only) without installing Home Assistant or
|
|
executing the integration's __init__ module.
|
|
|
|
The integration-package modules that stay Home-Assistant-free at import time
|
|
(currently connection.py) are also made importable — via a synthetic package
|
|
("nsh") that wraps custom_components/nvidia_shield WITHOUT executing its
|
|
__init__.py (that one imports Home Assistant). Only the stdlib-only modules
|
|
are loaded into it explicitly; their relative imports resolve normally.
|
|
"""
|
|
|
|
import importlib.util
|
|
import sys
|
|
import types
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO_ROOT / "custom_components" / "nvidia_shield"))
|
|
|
|
_SHIELD_DIR = REPO_ROOT / "custom_components" / "nvidia_shield"
|
|
_PKG = "nsh"
|
|
|
|
|
|
def _load_module(name: str, path: Path, search_paths: list[str] | None = None):
|
|
"""Import a source file under ``name`` without touching real packages."""
|
|
spec = importlib.util.spec_from_file_location(
|
|
name, path, submodule_search_locations=search_paths
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
# Synthetic integration package — no __init__.py execution (HA-free).
|
|
_root = types.ModuleType(_PKG)
|
|
_root.__path__ = [str(_SHIELD_DIR)]
|
|
sys.modules[_PKG] = _root
|
|
|
|
# Real subpackage __init__ (stdlib-only) so `from .ha_adb_common import ...`
|
|
# works through the synthetic package.
|
|
_load_module(
|
|
f"{_PKG}.ha_adb_common",
|
|
_SHIELD_DIR / "ha_adb_common" / "__init__.py",
|
|
[str(_SHIELD_DIR / "ha_adb_common")],
|
|
)
|
|
|
|
# Stdlib-only integration modules.
|
|
for _mod in ("const", "adb_session", "connection"):
|
|
_load_module(f"{_PKG}.{_mod}", _SHIELD_DIR / f"{_mod}.py")
|
|
|