from __future__ import annotations import pytest from src.hot_reloader import HotModule, HotReloader from unittest.mock import MagicMock, patch import sys import types def test_hot_module_dataclass_fields(): hm = HotModule( name="test_module", file_path="/path/to/test_module.py", state_keys=["attr1", "attr2"], delegation_targets=["method_a", "method_b"], ) assert hm.name == "test_module" assert hm.file_path == "/path/to/test_module.py" assert hm.state_keys == ["attr1", "attr2"] assert hm.delegation_targets == ["method_a", "method_b"] def test_hot_reloader_register_and_get(): HotReloader.HOT_MODULES.clear() hm = HotModule(name="test_mod", file_path="/fake/path.py", state_keys=[], delegation_targets=[]) HotReloader.register(hm) assert "test_mod" in HotReloader.HOT_MODULES assert HotReloader.HOT_MODULES["test_mod"] is hm def test_hot_reloader_register_duplicate_raises(): HotReloader.HOT_MODULES.clear() hm1 = HotModule(name="dup", file_path="/a.py", state_keys=[], delegation_targets=[]) HotReloader.register(hm1) with pytest.raises(ValueError, match="already registered"): HotReloader.register(hm1) def test_hot_reloader_is_error_state(): HotReloader.HOT_MODULES.clear() HotReloader.last_error = None HotReloader.is_error_state = False assert HotReloader.is_error_state is False def test_reload_unknown_module_returns_false(): HotReloader.HOT_MODULES.clear() HotReloader.register(HotModule(name="nonexistent_mod", file_path="/nonexistent.py", state_keys=[], delegation_targets=[])) app = MagicMock() result = HotReloader.reload("nonexistent_mod", app) assert result is False assert HotReloader.is_error_state is True assert HotReloader.last_error is not None def test_reload_success_clears_error_state(): HotReloader.HOT_MODULES.clear() test_mod = types.ModuleType("src._test_reload_mod_src") sys.modules["src._test_reload_mod_src"] = test_mod HotReloader.register(HotModule(name="src._test_reload_mod_src", file_path="/fake.py", state_keys=[], delegation_targets=[])) app = MagicMock() HotReloader.is_error_state = True HotReloader.last_error = "previous error" with patch("importlib.reload", return_value=test_mod): result = HotReloader.reload("src._test_reload_mod_src", app) assert result is True assert HotReloader.is_error_state is False assert HotReloader.last_error is None del sys.modules["src._test_reload_mod_src"] def test_reload_captures_and_restores_state_on_failure(): HotReloader.HOT_MODULES.clear() HotReloader.register(HotModule(name="bad_mod", file_path="/bad.py", state_keys=["_test_attr"], delegation_targets=[])) app = MagicMock() app._test_attr = "preserved_value" result = HotReloader.reload("bad_mod", app) assert result is False assert HotReloader.is_error_state is True assert app._test_attr == "preserved_value" def test_reload_all_success(): HotReloader.HOT_MODULES.clear() mod1 = types.ModuleType("hr_test_mod1") mod2 = types.ModuleType("hr_test_mod2") sys.modules["hr_test_mod1"] = mod1 sys.modules["hr_test_mod2"] = mod2 HotReloader.register(HotModule(name="hr_test_mod1", file_path="/fake1.py", state_keys=[], delegation_targets=[])) HotReloader.register(HotModule(name="hr_test_mod2", file_path="/fake2.py", state_keys=[], delegation_targets=[])) app = MagicMock() with patch("importlib.reload", return_value=mod1): result = HotReloader.reload_all(app) assert result is True assert HotReloader.is_error_state is False del sys.modules["hr_test_mod1"] del sys.modules["hr_test_mod2"] def test_reload_all_partial_failure(): HotReloader.HOT_MODULES.clear() mod1 = types.ModuleType("hr_test_mod1") sys.modules["hr_test_mod1"] = mod1 HotReloader.register(HotModule(name="hr_test_mod1", file_path="/fake1.py", state_keys=[], delegation_targets=[])) HotReloader.register(HotModule(name="hr_nonexistent", file_path="/nonexistent.py", state_keys=[], delegation_targets=[])) app = MagicMock() result = HotReloader.reload_all(app) assert result is False assert HotReloader.is_error_state is True del sys.modules["hr_test_mod1"]