ci: package __init__ files on the update path start the update E2Es
Importing a/b/c.py runs a/__init__.py and a/b/__init__.py first. The update-lane routing listed hermes_cli/local_runtime/processes.py (the v2026.9.24 updater imports it after the pull) but not the package init that import runs. #134116 changed what that init loads, its PR never ran e2e-upgrade, and main went red until #134242. classify_changes now routes the package inits above every module the e2e_upgrade and e2e_desktop_update lanes own, and above each shared hub, because a hub's inits run whenever the hub is imported. An init that is itself a hub stays out. The routing guard's import walker counts the package inits a module's imports run, so the next unrouted one fails it. This routes eight inits that were missing: local_runtime, gateway, cron, tools, tools/environments, plugins, hermes_platform, hermes_cli/observability.
This commit is contained in:
@@ -451,6 +451,22 @@ _E2E_LANES: dict[str, tuple[str, ...]] = {
|
||||
"hermes_cli/web_routers/status.py",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _with_package_inits(paths: tuple[str, ...]) -> tuple[str, ...]:
|
||||
"""Importing a/b/c.py runs a/__init__.py and a/b/__init__.py first, so a routed module
|
||||
routes the package inits above it. A shared hub's inits run whenever the hub is imported, so
|
||||
they are routed too; only an init that is itself a hub stays out."""
|
||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
inits = [f"{'/'.join(parts[:i])}/__init__.py"
|
||||
for p in (*paths, *sorted(_SHARED_HUBS)) if p.endswith(".py")
|
||||
for parts in [p.split("/")[:-1]] for i in range(1, len(parts) + 1)]
|
||||
return tuple(dict.fromkeys([*paths, *(i for i in inits if i not in _SHARED_HUBS
|
||||
and os.path.isfile(os.path.join(root, i)))]))
|
||||
|
||||
|
||||
for _lane in ("e2e_upgrade", "e2e_desktop_update"):
|
||||
_E2E_LANES[_lane] = _with_package_inits(_E2E_LANES[_lane])
|
||||
# The upgrade journeys are their own lane; editing one does not start ``e2e``.
|
||||
# The update suite shares apps/desktop/e2e/ with the core suite but not its specs.
|
||||
_E2E_LANE_EXCLUDES = {
|
||||
|
||||
@@ -650,6 +650,8 @@ def _imports(path: Path) -> frozenset[str]:
|
||||
base = ".".join([*parts, base] if base else parts)
|
||||
names.add(base)
|
||||
names.update(f"{base}.{a.name}" for a in node.names)
|
||||
# Importing a.b.c runs a/__init__.py and a/b/__init__.py first.
|
||||
names |= {".".join(n.split(".")[:i]) for n in names for i in range(1, n.count(".") + 1)}
|
||||
found = set()
|
||||
for name in names:
|
||||
target = _module_file(name)
|
||||
|
||||
@@ -17,6 +17,9 @@ from tests.ci.test_update_ci_routing import _REPO, _ci_run, _consumers_reached,
|
||||
# backup is the update transaction's pre-build step, not a build dependency.
|
||||
@pytest.mark.parametrize("path", [
|
||||
"hermes_cli/local_runtime/processes.py",
|
||||
# Importing processes runs the package init: v2026.9.24's update died there on a name
|
||||
# the init's eager imports needed from an already-loaded module.
|
||||
"hermes_cli/local_runtime/__init__.py",
|
||||
"agent/deadline.py",
|
||||
"agent/memory_provider.py",
|
||||
"pm/plugins_state.py",
|
||||
|
||||
Reference in New Issue
Block a user