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:
alt-glitch
2026-10-07 05:48:02 +05:30
committed by Mark S.
parent 53fefae5d7
commit cbffbeecf4
3 changed files with 21 additions and 0 deletions

View File

@@ -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 = {

View File

@@ -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)

View File

@@ -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",