mirror of
https://github.com/Matysh/houseplan-card
synced 2026-08-01 00:48:29 +00:00
The stub loader no longer poisons the HA harness
test_trail_recorder injected fake homeassistant modules into sys.modules at import time; every pytest_homeassistant_custom_component fixture in the same session then failed with 'module homeassistant has no attribute util'. The stubs now live inside a snapshot that is restored in a finally block.
This commit is contained in:
@@ -1,48 +1,79 @@
|
|||||||
"""Does TrailRecorder._on_state actually record? (the untested link)"""
|
"""TrailRecorder wiring: subscription callback, dialects, run end.
|
||||||
import sys, types, pathlib, time
|
|
||||||
|
|
||||||
# minimal HA stubs so trails.py imports without Home Assistant installed
|
Loads trails.py with STUBBED Home Assistant modules — but only inside a
|
||||||
ha = types.ModuleType("homeassistant"); sys.modules["homeassistant"] = ha
|
snapshot of sys.modules that is restored immediately afterwards. Injecting
|
||||||
core = types.ModuleType("homeassistant.core")
|
fake `homeassistant` modules globally poisons the real HA harness running in
|
||||||
core.HomeAssistant = object
|
the same pytest session (it did, once).
|
||||||
core.callback = lambda f: f
|
"""
|
||||||
sys.modules["homeassistant.core"] = core
|
import sys, types, pathlib, importlib.util
|
||||||
helpers = types.ModuleType("homeassistant.helpers"); sys.modules["homeassistant.helpers"] = helpers
|
|
||||||
er = types.ModuleType("homeassistant.helpers.entity_registry")
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||||
er.async_get = lambda hass: None
|
|
||||||
er.async_entries_for_device = lambda reg, dev: []
|
|
||||||
sys.modules["homeassistant.helpers.entity_registry"] = er
|
def _load_trails():
|
||||||
ev = types.ModuleType("homeassistant.helpers.event")
|
saved = {k: v for k, v in sys.modules.items() if k == "homeassistant" or k.startswith(("homeassistant.", "houseplan"))}
|
||||||
ev.async_call_later = lambda hass, delay, cb: (lambda: None)
|
try:
|
||||||
ev.async_track_state_change_event = lambda hass, ents, cb: (lambda: None)
|
for name in list(sys.modules):
|
||||||
sys.modules["homeassistant.helpers.event"] = ev
|
if name == "homeassistant" or name.startswith(("homeassistant.", "houseplan")):
|
||||||
st = types.ModuleType("homeassistant.helpers.storage")
|
del sys.modules[name]
|
||||||
class Store:
|
ha = types.ModuleType("homeassistant"); sys.modules["homeassistant"] = ha
|
||||||
|
core = types.ModuleType("homeassistant.core")
|
||||||
|
core.HomeAssistant = object
|
||||||
|
core.callback = lambda f: f
|
||||||
|
sys.modules["homeassistant.core"] = core
|
||||||
|
sys.modules["homeassistant.helpers"] = types.ModuleType("homeassistant.helpers")
|
||||||
|
er = types.ModuleType("homeassistant.helpers.entity_registry")
|
||||||
|
er.async_get = lambda hass: None
|
||||||
|
er.async_entries_for_device = lambda reg, dev: []
|
||||||
|
sys.modules["homeassistant.helpers.entity_registry"] = er
|
||||||
|
ev = types.ModuleType("homeassistant.helpers.event")
|
||||||
|
ev.async_call_later = lambda hass, delay, cb: (lambda: None)
|
||||||
|
ev.async_track_state_change_event = lambda hass, ents, cb: (lambda: None)
|
||||||
|
sys.modules["homeassistant.helpers.event"] = ev
|
||||||
|
stm = types.ModuleType("homeassistant.helpers.storage")
|
||||||
|
|
||||||
|
class _Store:
|
||||||
def __init__(self, *a, **k): pass
|
def __init__(self, *a, **k): pass
|
||||||
async def async_load(self): return None
|
async def async_load(self): return None
|
||||||
async def async_save(self, d): pass
|
async def async_save(self, d): pass
|
||||||
st.Store = Store
|
|
||||||
sys.modules["homeassistant.helpers.storage"] = st
|
|
||||||
const = types.ModuleType("custom_components.houseplan.const")
|
|
||||||
|
|
||||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
stm.Store = _Store
|
||||||
sys.path.insert(0, str(ROOT / "custom_components"))
|
sys.modules["homeassistant.helpers.storage"] = stm
|
||||||
pkg = types.ModuleType("houseplan"); pkg.__path__ = [str(ROOT / "custom_components" / "houseplan")]
|
pkg = types.ModuleType("houseplan"); pkg.__path__ = [str(ROOT / "custom_components" / "houseplan")]
|
||||||
sys.modules["houseplan"] = pkg
|
sys.modules["houseplan"] = pkg
|
||||||
c = types.ModuleType("houseplan.const"); c.DOMAIN = "houseplan"; sys.modules["houseplan.const"] = c
|
c = types.ModuleType("houseplan.const"); c.DOMAIN = "houseplan"
|
||||||
import importlib.util
|
sys.modules["houseplan.const"] = c
|
||||||
spec = importlib.util.spec_from_file_location("houseplan.trails", str(ROOT / "custom_components" / "houseplan" / "trails.py"))
|
spec = importlib.util.spec_from_file_location(
|
||||||
trails = importlib.util.module_from_spec(spec); sys.modules["houseplan.trails"] = trails
|
"houseplan.trails", str(ROOT / "custom_components" / "houseplan" / "trails.py")
|
||||||
spec.loader.exec_module(trails)
|
)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules["houseplan.trails"] = mod
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
|
finally:
|
||||||
|
for name in list(sys.modules):
|
||||||
|
if name == "homeassistant" or name.startswith(("homeassistant.", "houseplan")):
|
||||||
|
del sys.modules[name]
|
||||||
|
sys.modules.update(saved)
|
||||||
|
|
||||||
|
|
||||||
|
trails = _load_trails()
|
||||||
|
|
||||||
|
|
||||||
class S:
|
class S:
|
||||||
def __init__(self, state, attrs): self.state, self.attributes = state, attrs
|
def __init__(self, state, attrs): self.state, self.attributes = state, attrs
|
||||||
|
|
||||||
|
|
||||||
class States:
|
class States:
|
||||||
def __init__(self, d): self.d = d
|
def __init__(self, d): self.d = d
|
||||||
def get(self, k): return self.d.get(k)
|
def get(self, k): return self.d.get(k)
|
||||||
|
|
||||||
|
|
||||||
class Bus:
|
class Bus:
|
||||||
def __init__(self): self.fired = []
|
def __init__(self): self.fired = []
|
||||||
def async_fire(self, *a): self.fired.append(a)
|
def async_fire(self, *a): self.fired.append(a)
|
||||||
|
|
||||||
|
|
||||||
class Hass:
|
class Hass:
|
||||||
def __init__(self, states): self.states, self.bus, self.data = States(states), Bus(), {}
|
def __init__(self, states): self.states, self.bus, self.data = States(states), Bus(), {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user