fix(tests): test_hermes_phase2_dispatch exec-load needs escalation + __name__

Phase 3 escalation ladder added `from .escalation import ...` to
executor.py. The phase-2 dispatch tests load executor.py via
`exec(compile(src, ...))` with the relative import rewritten — this
broke because (a) the rewrite didn't know about escalation and (b) the
exec namespace lacked `__name__`, which executor.py needs at import
time for `logging.getLogger(__name__)`.

Fix both in all 8 exec sites:
- Rewrite both `from .providers import` AND `from .escalation import`
- Pre-register escalation + providers in sys.modules under the fake
  package name
- Seed the exec namespace with `__name__ = "hermes_executor_under_test"`

54/54 hermes tests pass (28 escalation truth-table + 6 ladder-integration
+ 20 existing phase-2 dispatch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
rabbitblood 2026-04-16 02:43:02 -07:00
parent 7d732cec3c
commit f76ddba0f5

View File

@ -13,10 +13,14 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
# Load providers.py directly (same pattern as test_hermes_providers.py)
# Load providers.py + escalation.py directly (same pattern as
# test_hermes_providers.py). The escalation module landed with the
# ladder work — it's now imported by executor.py, so the inline-exec
# pattern below has to find both modules at top level.
_HERMES_DIR = Path(__file__).parent.parent / "adapters" / "hermes"
sys.path.insert(0, str(_HERMES_DIR))
import providers # type: ignore # noqa: E402
import escalation # type: ignore # noqa: E402
def _make_executor(provider_name: str):
@ -44,15 +48,22 @@ def _make_executor(provider_name: str):
# which fails under direct spec_from_file_location. Monkey-patch sys.modules
# so the relative import resolves to our directly-loaded providers module.
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
# Also alias the package-style import path so `from .providers import X`
# inside executor.py finds it.
# and `from .escalation import X` inside executor.py find them.
pkg_name = "hermes_executor_under_test"
sys.modules.setdefault(pkg_name, MagicMock())
sys.modules[pkg_name].providers = providers # type: ignore
# Read + compile executor.py with the relative import rewritten
sys.modules[pkg_name].escalation = escalation # type: ignore
# Read + compile executor.py with relative imports rewritten to match
# the sibling-import setup above.
src = (_HERMES_DIR / "executor.py").read_text()
src = src.replace("from .providers import", "from providers import")
ns: dict = {}
src = src.replace("from .escalation import", "from escalation import")
# The exec'd module needs `__name__` in its globals because executor.py
# calls ``logging.getLogger(__name__)`` at import time. Without this the
# exec fails with `KeyError: "'__name__' not in globals"`.
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
HermesA2AExecutor = ns["HermesA2AExecutor"]
return HermesA2AExecutor(
@ -148,8 +159,16 @@ def test_history_to_openai_messages_empty_history():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
HermesA2AExecutor = ns["HermesA2AExecutor"]
@ -162,8 +181,16 @@ def test_history_to_openai_messages_multi_turn():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
HermesA2AExecutor = ns["HermesA2AExecutor"]
@ -182,8 +209,16 @@ def test_history_to_anthropic_messages_same_as_openai():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
HermesA2AExecutor = ns["HermesA2AExecutor"]
@ -198,8 +233,16 @@ def test_history_to_gemini_contents_uses_model_role_and_parts_wrapper():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
HermesA2AExecutor = ns["HermesA2AExecutor"]
@ -279,8 +322,16 @@ def test_executor_accepts_config_path_kwarg():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
HermesA2AExecutor = ns["HermesA2AExecutor"]
cfg = providers.PROVIDERS["openai"]
@ -305,8 +356,16 @@ def test_create_executor_forwards_config_path():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
create_executor = ns["create_executor"]
@ -388,8 +447,16 @@ def test_create_executor_passes_provider_cfg():
import importlib.util
src = (_HERMES_DIR / "executor.py").read_text().replace(
"from .providers import", "from providers import"
).replace(
"from .escalation import", "from escalation import"
)
ns: dict = {}
# `__name__` needed because executor.py does logging.getLogger(__name__)
# at import time. `escalation` + `providers` must also be importable
# at the top level — the caller handles sys.path for that.
sys.modules.setdefault("hermes_executor_under_test", MagicMock())
sys.modules["hermes_executor_under_test.providers"] = providers
sys.modules["hermes_executor_under_test.escalation"] = escalation
ns: dict = {"__name__": "hermes_executor_under_test"}
exec(compile(src, str(_HERMES_DIR / "executor.py"), "exec"), ns)
create_executor = ns["create_executor"]