From 65e79880c81e8bbefac5d47b03cc29455437d0b5 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Wed, 23 Sep 2026 07:11:02 -0700 Subject: [PATCH] ci(e2e): build the Ink TUI for the terminal suite; run the upgrade suite in its own job tests/e2e/core/terminal drives the real `hermes --tui` over a PTY, so the e2e job now installs the Node workspaces and builds ui-tui, and HERMES_E2E_REQUIRE_TUI=1 makes a missing build fail instead of skip. tests/e2e/core/upgrade runs a real N-1 -> HEAD `hermes update`: it needs full history + tags, bubblewrap (every updater runs sandboxed so it can never reach a real gateway or systemd), the warm uv cache, and up to ~15 min for one file. It gets its own 60-minute job instead of stretching the e2e job. --- .github/workflows/tests.yml | 112 +++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 191bf29d02..a7ca83ed6c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -182,6 +182,13 @@ jobs: with: node-version: 26 + - name: Install Node deps and build the Ink TUI + # tests/e2e/core/terminal drives the real `hermes --tui` over a PTY; + # HERMES_E2E_REQUIRE_TUI=1 below turns a missing build into a failure. + uses: ./.github/actions/retry + with: + command: npm ci --ignore-scripts --no-audit --no-fund && npm run build --prefix ui-tui + - name: Install ripgrep (prebuilt binary) run: | set -euo pipefail @@ -253,9 +260,11 @@ jobs: # One subprocess per file, in parallel: the core suites spawn real # processes (serve, gateway, tui_gateway, MCP servers, SQLite writers) # and must not share interpreter state. + # tests/e2e/core/upgrade runs in its own job (e2e-upgrade) below. run: | source .venv/bin/activate - scripts/run_tests.sh --include-integration tests/e2e + mapfile -t files < <(find tests/e2e -name 'test_*.py' -not -path 'tests/e2e/core/upgrade/*' | sort) + scripts/run_tests.sh --include-integration "${files[@]}" env: # Multi-process episodes (torture chamber, compaction kill -9, # gateway liveness, delivery exactly-once through a real gateway, @@ -265,6 +274,107 @@ jobs: # exactly-once/compaction suites are race detectors, and a rare # corruption that passes on retry is still a corruption. HERMES_TEST_FILE_RETRIES: "0" + HERMES_E2E_REQUIRE_TUI: "1" + OPENROUTER_API_KEY: "" + OPENAI_API_KEY: "" + NOUS_API_KEY: "" + + e2e-upgrade: + # tests/e2e/core/upgrade: a real N-1 -> HEAD `hermes update` (clean, + # autostash, killed mid-pull / mid-deps, offline), fresh-process import and + # entrypoint smoke, and the config round-trip property matrix. Its own job: + # it needs full history + tags, bubblewrap, and one file runs ~5-15 min. + runs-on: ubuntu-latest-32-core + timeout-minutes: 60 + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + # N-1 = `git describe --tags --abbrev=0 HEAD~1`. + fetch-depth: 0 + fetch-tags: true + + - name: Set up Node + # `hermes update` builds the web UI / TUI workspaces. + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 26 + + - name: Install ripgrep (prebuilt binary) + run: | + set -euo pipefail + RG_VERSION=15.1.0 + RG_SHA256=1c9297be4a084eea7ecaedf93eb03d058d6faae29bbc57ecdaf5063921491599 + RG_TARBALL=ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl.tar.gz + curl -sSfL --retry 3 --retry-delay 5 -o "$RG_TARBALL" \ + "https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${RG_TARBALL}" + echo "${RG_SHA256} ${RG_TARBALL}" | sha256sum -c - + tar -xzf "$RG_TARBALL" + sudo mv "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl/rg" /usr/local/bin/rg + rm -rf "$RG_TARBALL" "ripgrep-${RG_VERSION}-x86_64-unknown-linux-musl" + rg --version + + - name: Install uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # 8.2.0 + with: + # Pin the uv version: unpinned, setup-uv resolves "latest" by + # fetching a manifest from raw.githubusercontent.com on EVERY job — + # a transient fetch failure fails the whole job (2026-07-28 slice-5 + # incident). Pinned, the binary downloads directly; no manifest hop. + # Newer than the unit job's pin on purpose: 0.9.28 only knows CPython + # 3.11.14, whose bundled SQLite has the WAL-reset bug, so Hermes runs + # state.db in DELETE mode and the WAL torture chamber would skip. + version: "0.12.13" + # Persist uv's download/wheel cache (~/.cache/uv) across runs. + # Keyed on the dependency manifests, so the cache is reused until + # pyproject.toml or uv.lock changes. `uv sync` still runs every + # time, but resolves from the warm cache instead of re-downloading + # and re-building wheels. + enable-cache: true + cache-dependency-glob: | + pyproject.toml + uv.lock + + - name: Set up Python 3.11 + run: uv python install 3.11.15 + + - name: Install dependencies + # `uv sync --locked` installs the exact pinned set from uv.lock (and + # fails if the lock is out of sync with pyproject.toml), giving a + # reproducible env. It also creates .venv itself, so no separate + # `uv venv` step is needed. + # + # Same extras as the test job's sync above: the hermetic test env + # forbids mid-run pip installs (HERMES_DISABLE_LAZY_INSTALLS=1 in + # tests/conftest.py), so lazy-install SDKs exercised by tests must be + # in the venv up front. + uses: ./.github/actions/retry + with: + command: uv sync --locked --python 3.11.15 --extra all --extra dev --extra anthropic --extra mistral --extra fal --extra modal --extra daytona --extra parallel-web + + - name: Install bubblewrap + # Every spawned updater runs in bwrap (own PID namespace, no user + # systemd bus, only the test tmp writable). Ubuntu 24.04 restricts + # unprivileged user namespaces, which bwrap needs. + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq bubblewrap + sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true + bwrap --unshare-pid --dev-bind / / true && echo "bwrap ok" + + - name: Require a WAL-capable SQLite + # The state.db suites skip on a WAL-reset-vulnerable SQLite; fail + # instead of reporting green over zero coverage. + run: | + source .venv/bin/activate + python -c "import sqlite3, hermes_state_wal as w; print('sqlite', sqlite3.sqlite_version); assert not w.is_sqlite_wal_reset_vulnerable()" + + - name: Run upgrade e2e tests + run: | + source .venv/bin/activate + scripts/run_tests.sh --include-integration tests/e2e/core/upgrade + env: + HERMES_TEST_FILE_TIMEOUT: "3000" OPENROUTER_API_KEY: "" OPENAI_API_KEY: "" NOUS_API_KEY: ""