0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
402 lines
15 KiB
Python
402 lines
15 KiB
Python
"""Docker e2e cases for ``headroom init``.
|
|
|
|
Every case is described declaratively with :class:`Case` from
|
|
``e2e/_lib/harness.py``. Three groups run in order:
|
|
|
|
1. **existing sequence**: preserves the original scenario that exercised
|
|
``headroom init claude`` (local) -> ``init -g copilot`` (global) ->
|
|
``init codex`` (local), sharing scratch state so manifest-merge is
|
|
exercised end-to-end.
|
|
2. **bare ``init -g`` detection**: verifies the UX regression from #245
|
|
stays fixed — both "no shims found" (friendly error, exit 1) and
|
|
"all shims found" (exit 0, all four agents configured).
|
|
3. **per-subcommand**: one case per ``init -g <agent>`` with only that
|
|
agent's shim on PATH, so the explicit path is covered independently.
|
|
|
|
The fourth group covers ``--verbose`` output going to stderr.
|
|
|
|
Run directly: ``python e2e/init/run.py`` (inside the Docker image built
|
|
from ``e2e/init/Dockerfile``).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import tomllib
|
|
except ModuleNotFoundError: # Python < 3.11
|
|
import tomli as tomllib # type: ignore[no-redef]
|
|
|
|
# Add repo root to sys.path so the harness import works whether the file is
|
|
# invoked as ``python e2e/init/run.py`` or ``python -m e2e.init.run``.
|
|
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
if str(_REPO_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(_REPO_ROOT))
|
|
|
|
from e2e._lib import ( # noqa: E402
|
|
Case,
|
|
CaseContext,
|
|
run_case_sequence,
|
|
run_cases,
|
|
)
|
|
from headroom.cli import init as init_cli # noqa: E402
|
|
from headroom.install.runtime import resolve_headroom_command # noqa: E402
|
|
|
|
# ----- helpers reused across cases --------------------------------------------
|
|
|
|
# Docker image builds the workspace at /workspace; the marketplace source
|
|
# falls back to that repo checkout when a local marketplace manifest is found.
|
|
REPO_ROOT_IN_CONTAINER = Path("/workspace")
|
|
|
|
|
|
def _expected_headroom_mcp_calls(proxy_url: str) -> list[list[str]]:
|
|
# The harness restores PATH before assertions, but `headroom init` ran with a
|
|
# scrubbed PATH where the console-script entrypoint may be unavailable.
|
|
prefix = [
|
|
"mcp",
|
|
"add",
|
|
"headroom",
|
|
"-s",
|
|
"user",
|
|
"-e",
|
|
f"HEADROOM_PROXY_URL={proxy_url}",
|
|
"--",
|
|
]
|
|
variants = [
|
|
[*prefix, *resolve_headroom_command(), "mcp", "serve"],
|
|
[*prefix, sys.executable, "-m", "headroom.cli", "mcp", "serve"],
|
|
]
|
|
deduped: list[list[str]] = []
|
|
for variant in variants:
|
|
if variant not in deduped:
|
|
deduped.append(variant)
|
|
return deduped
|
|
|
|
|
|
def _read_jsonl(path: Path) -> list[dict[str, object]]:
|
|
if not path.exists():
|
|
return []
|
|
return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
|
|
|
|
|
def _expect_hook_command(command: str, profile: str) -> None:
|
|
if "init hook ensure" not in command:
|
|
raise AssertionError(f"missing 'init hook ensure' in: {command}")
|
|
if f"--profile {profile}" not in command:
|
|
raise AssertionError(f"missing '--profile {profile}' in: {command}")
|
|
|
|
|
|
def _read_manifest(home: Path, profile: str) -> dict[str, object]:
|
|
path = home / ".headroom" / "deploy" / profile / "manifest.json"
|
|
if not path.exists():
|
|
raise AssertionError(f"Expected manifest at {path}")
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def _expect_codex_hooks_feature(config: str) -> None:
|
|
parsed = tomllib.loads(config)
|
|
features = parsed.get("features")
|
|
if not isinstance(features, dict) or features.get("hooks") is not True:
|
|
raise AssertionError("Codex config should enable hooks")
|
|
if "codex_hooks" in features:
|
|
raise AssertionError("Codex config should not keep deprecated codex_hooks")
|
|
|
|
|
|
# ----- existing-flow assertions (ported verbatim from the old run.py) ---------
|
|
|
|
|
|
def _verify_claude_local(ctx: CaseContext) -> None:
|
|
settings_path = ctx.project / ".claude" / "settings.local.json"
|
|
settings = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
if settings["env"]["ANTHROPIC_BASE_URL"] != "http://127.0.0.1:9011":
|
|
raise AssertionError(
|
|
f"Claude local settings should point at port 9011, got "
|
|
f"{settings['env']['ANTHROPIC_BASE_URL']!r}"
|
|
)
|
|
session_start = settings["hooks"]["SessionStart"][0]["hooks"][0]["command"]
|
|
pre_tool = settings["hooks"]["PreToolUse"][0]["hooks"][0]["command"]
|
|
profile = init_cli._local_profile(ctx.project)
|
|
_expect_hook_command(session_start, profile)
|
|
_expect_hook_command(pre_tool, profile)
|
|
|
|
manifest = _read_manifest(ctx.home, profile)
|
|
if "claude" not in manifest["targets"]:
|
|
raise AssertionError(
|
|
f"Claude init should register the claude target, got {manifest['targets']}"
|
|
)
|
|
|
|
claude_calls = [
|
|
record["argv"] for record in _read_jsonl(ctx.shim_log) if record["tool"] == "claude"
|
|
]
|
|
# `init` auto-registers the headroom MCP server after the marketplace
|
|
# install (see d9d8972 — keeps `[Retrieve more: hash=…]` markers from
|
|
# being dead pointers for users who never ran `headroom mcp install`).
|
|
# The `-e HEADROOM_PROXY_URL=…` arg is only emitted when the proxy
|
|
# port differs from the 8787 default; this case uses --port 9011.
|
|
expected_prefix = [
|
|
["plugin", "marketplace", "add", str(REPO_ROOT_IN_CONTAINER)],
|
|
["plugin", "install", "headroom@headroom-marketplace", "--scope", "local"],
|
|
]
|
|
expected_mcp_calls = _expected_headroom_mcp_calls("http://127.0.0.1:9011")
|
|
if (
|
|
len(claude_calls) != 3
|
|
or claude_calls[:2] != expected_prefix
|
|
or claude_calls[2] not in expected_mcp_calls
|
|
):
|
|
raise AssertionError(f"Unexpected Claude install commands: {claude_calls}")
|
|
|
|
|
|
def _verify_copilot_global(ctx: CaseContext) -> None:
|
|
config = json.loads((ctx.home / ".copilot" / "config.json").read_text(encoding="utf-8"))
|
|
if "SessionStart" not in config["hooks"]:
|
|
raise AssertionError("Copilot config missing SessionStart hooks")
|
|
if "PreToolUse" not in config["hooks"]:
|
|
raise AssertionError("Copilot config missing PreToolUse hooks")
|
|
session_start = config["hooks"]["SessionStart"][0]["command"]
|
|
_expect_hook_command(session_start, "init-user")
|
|
|
|
for shell_file in (ctx.home / ".bashrc", ctx.home / ".zshrc", ctx.home / ".profile"):
|
|
content = shell_file.read_text(encoding="utf-8")
|
|
for literal in (
|
|
'export COPILOT_PROVIDER_TYPE="openai"',
|
|
'export COPILOT_PROVIDER_BASE_URL="http://127.0.0.1:9005/v1"',
|
|
'export COPILOT_PROVIDER_WIRE_API="completions"',
|
|
):
|
|
if literal not in content:
|
|
raise AssertionError(f"{shell_file.name} missing {literal!r}")
|
|
|
|
copilot_calls = [
|
|
record["argv"] for record in _read_jsonl(ctx.shim_log) if record["tool"] == "copilot"
|
|
]
|
|
expected = [
|
|
["plugin", "marketplace", "add", str(REPO_ROOT_IN_CONTAINER)],
|
|
["plugin", "install", "headroom@headroom-marketplace"],
|
|
]
|
|
if copilot_calls != expected:
|
|
raise AssertionError(f"Unexpected Copilot install commands: {copilot_calls}")
|
|
|
|
|
|
def _verify_codex_local(ctx: CaseContext) -> None:
|
|
config = (ctx.project / ".codex" / "config.toml").read_text(encoding="utf-8")
|
|
hooks = json.loads((ctx.project / ".codex" / "hooks.json").read_text(encoding="utf-8"))
|
|
profile = init_cli._local_profile(ctx.project)
|
|
|
|
if 'base_url = "http://127.0.0.1:9012/v1"' not in config:
|
|
raise AssertionError("Codex config should point at the requested proxy port (9012)")
|
|
if 'env_key = "OPENAI_API_KEY"' in config:
|
|
raise AssertionError("Codex local init should preserve OAuth and never inject env_key")
|
|
# Bug 3 (#406): requires_openai_auth must be absent from headroom provider blocks.
|
|
if "requires_openai_auth" in config:
|
|
raise AssertionError(
|
|
"Codex local init must NOT inject requires_openai_auth into the headroom provider block"
|
|
)
|
|
if "supports_websockets = true" not in config:
|
|
raise AssertionError("Codex local init missing 'supports_websockets = true'")
|
|
if config.count("[features]") != 1:
|
|
raise AssertionError("Codex config should keep a single [features] table")
|
|
_expect_codex_hooks_feature(config)
|
|
command = hooks["hooks"]["SessionStart"][0]["hooks"][0]["command"]
|
|
_expect_hook_command(command, profile)
|
|
|
|
manifest = _read_manifest(ctx.home, profile)
|
|
targets = manifest["targets"]
|
|
if set(targets) != {"claude", "codex"}:
|
|
raise AssertionError(f"Unexpected merged targets: {targets}")
|
|
|
|
|
|
# ----- new cases (issue #245 fix + per-subcommand coverage) -------------------
|
|
|
|
|
|
def _verify_claude_global(ctx: CaseContext) -> None:
|
|
settings = json.loads((ctx.home / ".claude" / "settings.json").read_text(encoding="utf-8"))
|
|
if settings["env"]["ANTHROPIC_BASE_URL"] != "http://127.0.0.1:8787":
|
|
raise AssertionError(
|
|
f"Claude user settings should default to port 8787, got "
|
|
f"{settings['env']['ANTHROPIC_BASE_URL']!r}"
|
|
)
|
|
_expect_hook_command(
|
|
settings["hooks"]["SessionStart"][0]["hooks"][0]["command"],
|
|
init_cli._GLOBAL_PROFILE,
|
|
)
|
|
|
|
|
|
def _verify_codex_global(ctx: CaseContext) -> None:
|
|
config = (ctx.home / ".codex" / "config.toml").read_text(encoding="utf-8")
|
|
if 'base_url = "http://127.0.0.1:8787/v1"' not in config:
|
|
raise AssertionError("Codex user config should point at port 8787 by default")
|
|
if 'env_key = "OPENAI_API_KEY"' in config:
|
|
raise AssertionError("Codex global init should preserve OAuth and never inject env_key")
|
|
# Bug 3 (#406): requires_openai_auth must be absent from headroom provider blocks.
|
|
if "requires_openai_auth" in config:
|
|
raise AssertionError(
|
|
"Codex global init must NOT inject requires_openai_auth into the headroom provider block"
|
|
)
|
|
if "supports_websockets = true" not in config:
|
|
raise AssertionError("Codex global init missing 'supports_websockets = true'")
|
|
_expect_codex_hooks_feature(config)
|
|
hooks = json.loads((ctx.home / ".codex" / "hooks.json").read_text(encoding="utf-8"))
|
|
_expect_hook_command(
|
|
hooks["hooks"]["SessionStart"][0]["hooks"][0]["command"],
|
|
init_cli._GLOBAL_PROFILE,
|
|
)
|
|
|
|
|
|
# ----- case tables ------------------------------------------------------------
|
|
|
|
|
|
def existing_sequence_cases() -> list[Case]:
|
|
"""Preserves the original run.py scenario in one shared scratch."""
|
|
|
|
return [
|
|
Case(
|
|
name="seq_claude_local",
|
|
argv=["init", "--port", "9011", "claude"],
|
|
shims={"claude": "record-args", "copilot": "record-args"},
|
|
expected_exit=0,
|
|
expected_stdout_contains=["Configured Claude Code (local scope)"],
|
|
extra_assertions=[_verify_claude_local],
|
|
),
|
|
Case(
|
|
name="seq_copilot_global",
|
|
argv=["init", "-g", "--port", "9005", "--backend", "openai", "copilot"],
|
|
shims={}, # reuse shims from prior case in the sequence
|
|
expected_exit=0,
|
|
expected_stdout_contains=["Configured GitHub Copilot CLI (user scope)"],
|
|
extra_assertions=[_verify_copilot_global],
|
|
),
|
|
Case(
|
|
name="seq_codex_local",
|
|
argv=["init", "--port", "9012", "codex"],
|
|
shims={},
|
|
expected_exit=0,
|
|
expected_stdout_contains=["Configured Codex (local scope)"],
|
|
extra_assertions=[_verify_codex_local],
|
|
),
|
|
]
|
|
|
|
|
|
def bare_init_g_cases() -> list[Case]:
|
|
"""Bare ``headroom init -g`` — the direct coverage of issue #245."""
|
|
|
|
return [
|
|
Case(
|
|
name="bare_init_g_no_shims",
|
|
argv=["init", "-g"],
|
|
shims={}, # nothing on PATH
|
|
expected_exit=1,
|
|
expected_stderr_contains=[
|
|
# every target should be listed so the user knows what was tried
|
|
"claude",
|
|
"codex",
|
|
"copilot",
|
|
"openclaw",
|
|
# concrete escape hatch — exactly what the user should type next
|
|
"headroom init -g claude",
|
|
# confirm -g itself is still the right flag
|
|
"-g",
|
|
],
|
|
),
|
|
Case(
|
|
name="bare_init_g_with_all_shims",
|
|
argv=["init", "-g"],
|
|
shims={
|
|
"claude": "record-args",
|
|
"codex": "noop",
|
|
"copilot": "record-args",
|
|
"openclaw": "noop",
|
|
},
|
|
expected_exit=0,
|
|
expected_stdout_contains=[
|
|
"Configured Claude Code (user scope)",
|
|
"Configured GitHub Copilot CLI (user scope)",
|
|
"Configured Codex (user scope)",
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
def per_subcommand_cases() -> list[Case]:
|
|
"""One case per ``headroom init -g <agent>`` with only that agent's shim."""
|
|
|
|
return [
|
|
Case(
|
|
name="init_g_claude_explicit",
|
|
argv=["init", "-g", "claude"],
|
|
shims={"claude": "record-args"},
|
|
expected_exit=0,
|
|
expected_stdout_contains=["Configured Claude Code (user scope)"],
|
|
expected_files=["{home}/.claude/settings.json"],
|
|
extra_assertions=[_verify_claude_global],
|
|
),
|
|
Case(
|
|
name="init_g_codex_explicit",
|
|
argv=["init", "-g", "codex"],
|
|
shims={"codex": "noop"},
|
|
expected_exit=0,
|
|
expected_stdout_contains=["Configured Codex (user scope)"],
|
|
expected_files=[
|
|
"{home}/.codex/config.toml",
|
|
"{home}/.codex/hooks.json",
|
|
],
|
|
extra_assertions=[_verify_codex_global],
|
|
),
|
|
Case(
|
|
name="init_g_copilot_explicit",
|
|
argv=["init", "-g", "copilot"],
|
|
shims={"copilot": "record-args"},
|
|
expected_exit=0,
|
|
expected_stdout_contains=["Configured GitHub Copilot CLI (user scope)"],
|
|
expected_files=["{home}/.copilot/config.json"],
|
|
),
|
|
# openclaw delegates to `headroom wrap openclaw` which has its own
|
|
# (more expensive) init path and isn't stubbable with a simple shim.
|
|
# We assert it fails fast with a clear error when not installed, and
|
|
# rely on the `bare_init_g_with_all_shims` case (which uses a noop
|
|
# openclaw shim + claude/codex/copilot shims) to cover the success
|
|
# path alongside the other agents.
|
|
Case(
|
|
name="init_g_openclaw_missing",
|
|
argv=["init", "-g", "openclaw"],
|
|
shims={},
|
|
expected_exit=1,
|
|
),
|
|
]
|
|
|
|
|
|
def verbose_cases() -> list[Case]:
|
|
"""Verbose flag smoke tests — debug lines should appear on stderr."""
|
|
|
|
return [
|
|
Case(
|
|
name="init_verbose_no_shims",
|
|
argv=["init", "-v", "-g"],
|
|
shims={},
|
|
expected_exit=1,
|
|
expected_stderr_contains=[
|
|
# A few structural markers from the verbose log. Kept loose so
|
|
# minor wording tweaks don't break the test.
|
|
"detect_init_targets",
|
|
"claude",
|
|
"global_scope=True",
|
|
],
|
|
),
|
|
]
|
|
|
|
|
|
def main() -> None:
|
|
rc = 0
|
|
rc |= run_case_sequence(existing_sequence_cases(), label="existing-sequence")
|
|
rc |= run_cases(bare_init_g_cases())
|
|
rc |= run_cases(per_subcommand_cases())
|
|
rc |= run_cases(verbose_cases())
|
|
if rc != 0:
|
|
raise SystemExit(rc)
|
|
print("[e2e] init e2e completed successfully", flush=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|