From b1c5fd00d2f767b5d2c3170be72586a87f316575 Mon Sep 17 00:00:00 2001 From: Parfii-bot Date: Wed, 22 Apr 2026 18:37:55 +0800 Subject: [PATCH] test(v0.21): Docker battle-test infra for install.sh on fresh ubuntu:24.04 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/battle/Dockerfile.install-test — ubuntu:24.04 + deps (git, curl, ca-certificates, build-essential, jq, pandoc, rustup) tests/battle/battle-entry.sh — ENTRYPOINT: runs install.sh with $PROFILE (default minimal), then verify.sh tests/battle/verify.sh — POSIX sh gate: blocks >= 79, skills >= 39, top hooks >= 10, _lib hooks >= 2, test-gate.sh exits 0, settings.json valid JSON tests/battle/README.md — build + run docs SHIP-BLOCKER FOUND (for follow-up fix commit): kei-artifact crate fails to compile on fresh install because install/lib-primitives.sh::copy_rust_primitive copies only Cargo.toml + src/ + tests/. Crate has sibling schemas/ dir with 5 JSON files that src/schemas.rs include_str!s at compile time. Missing → cargo build error, install exits 0 (soft-fail design) but full profile only produces 6/25 binaries instead of 25/25. Real stdout verified: minimal: 80 blocks, 39 skills, 10 hooks, 3 _lib — exit 0 ✓ dev: same counts — exit 0 ✓ BUT 3/8 binaries (kei-artifact fail) full: same counts — exit 0 ✓ BUT 6/25 binaries Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + tests/battle/Dockerfile.install-test | 57 ++++++++++++++++++++++++++++ tests/battle/README.md | 39 +++++++++++++++++++ tests/battle/battle-entry.sh | 28 ++++++++++++++ tests/battle/verify.sh | 50 ++++++++++++++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 tests/battle/Dockerfile.install-test create mode 100644 tests/battle/README.md create mode 100755 tests/battle/battle-entry.sh create mode 100755 tests/battle/verify.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c73303..feed60e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ _primitives/_rust/target/release/kei-changelog \ > ships must be replaced with the real commit summary before release. ### Added +- **tests/battle:** Docker-based clean-Ubuntu install test — `tests/battle/Dockerfile.install-test` + `verify.sh` + `battle-entry.sh` + README. Builds a fresh `ubuntu:24.04` image, runs `install.sh --profile=` under `--yes`, then asserts post-install counts (blocks ≥ 79, skills ≥ 39, top hooks ≥ 10, `_lib` hooks ≥ 2), runs `hooks/_lib/test-gate.sh`, and validates `settings.json`. First real-world "does it work on a fresh machine?" signal — CI previously only ran `--no-execute` dry-runs. v0.21 ship-blocker for any profile that regresses. - **primitives (v0.20 — brain schema v2 + per-client hint):** - Brain schema v2 with per-platform `mcp_server` dispatch — a single brain directory can now host binaries for darwin-arm64/darwin-x64/linux-x64/linux-arm64/windows-x64 and `keisei attach` picks the right one automatically. Schema v1 (single string) still accepted for backward-compat. - `ClientAdapter::post_attach_hint()` — per-client reload instruction, no more hardcoded Claude-Code string in the orchestrator. diff --git a/tests/battle/Dockerfile.install-test b/tests/battle/Dockerfile.install-test new file mode 100644 index 0000000..033da14 --- /dev/null +++ b/tests/battle/Dockerfile.install-test @@ -0,0 +1,57 @@ +# tests/battle/Dockerfile.install-test +# Battle-test: run KeiSeiKit install.sh on a clean Ubuntu 24.04 container. +# Validates: install succeeds, block/skill/hook counts match README, hook +# test-gate passes, settings.json (if any) is valid JSON. +# +# Build from repo root: +# docker build -t keisei-battle:latest -f tests/battle/Dockerfile.install-test . +# +# Run (default: minimal profile): +# docker run --rm keisei-battle:latest +# +# Override profile: +# docker run --rm -e PROFILE=dev keisei-battle:latest +# docker run --rm -e PROFILE=full keisei-battle:latest +# +# Env: +# PROFILE one of minimal|core|dev|full (default: minimal) +# KEI_SKIP_RUST_BUILD 1 = skip cargo build for primitives (assembler +# still builds; install.sh always builds it) + +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + CARGO_HOME=/root/.cargo \ + RUSTUP_HOME=/root/.rustup \ + PATH=/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +# Baseline deps that a "fresh" user machine would have after apt install. +# build-essential: cc/ld for cargo. jq: install.sh HARD prereq. pandoc: +# soft prereq for tomd. git, curl, ca-certificates: kit + rustup download. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + git curl ca-certificates build-essential jq pandoc \ + && rm -rf /var/lib/apt/lists/* + +# Rust toolchain — Ubuntu 24.04 ships rustc 1.75; _assembler/Cargo.toml uses +# edition = "2024" which needs >= 1.85. Use rustup to get stable. +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain stable --profile minimal --no-modify-path \ + && rustc --version && cargo --version + +# Copy the kit. .dockerignore excludes target/ so the image stays slim. +WORKDIR /opt/keiseikit +COPY . /opt/keiseikit/ + +# Install verify + entry scripts (path is hardcoded so they remain runnable +# whatever CWD the user sets via -w). +COPY tests/battle/verify.sh /usr/local/bin/verify.sh +COPY tests/battle/battle-entry.sh /usr/local/bin/battle-entry.sh +RUN chmod +x /usr/local/bin/verify.sh \ + /usr/local/bin/battle-entry.sh \ + /opt/keiseikit/install.sh + +ENV PROFILE=minimal +ENTRYPOINT ["/usr/local/bin/battle-entry.sh"] diff --git a/tests/battle/README.md b/tests/battle/README.md new file mode 100644 index 0000000..8d96f5b --- /dev/null +++ b/tests/battle/README.md @@ -0,0 +1,39 @@ +# tests/battle — Clean-Ubuntu Install Test + +Validates `install.sh` on a fresh `ubuntu:24.04` container. CI only runs +`--no-execute` dry-runs; this battle-test actually executes the installer. + +## Run + +From repo root: + +```bash +docker build -t keisei-battle:latest -f tests/battle/Dockerfile.install-test . +docker run --rm keisei-battle:latest # minimal +docker run --rm -e PROFILE=core keisei-battle:latest +docker run --rm -e PROFILE=dev keisei-battle:latest +docker run --rm -e PROFILE=full keisei-battle:latest +``` + +Container exits 0 = green. Any other code = investigate stdout. + +## What it asserts (verify.sh) + +- `~/.claude/agents/_blocks` ≥ 79 +- `~/.claude/skills` ≥ 39 +- `~/.claude/hooks/*.sh` ≥ 10 top-level +- `~/.claude/hooks/_lib/*.sh` ≥ 2 (gate.sh + test-gate.sh, v0.17) +- `hooks/_lib/test-gate.sh` self-test passes (11/11) +- `settings.json` (if created) parses as valid JSON + +## Known quirks (2026-04-22) + +- **`kei-artifact` crate fails** on `dev`/`full`: `copy_rust_primitive` + (install/lib-primitives.sh) copies `src/` + `tests/` only — misses + sibling `schemas/`, so `include_str!("../schemas/*.json")` breaks. + Install still exits 0 (build is soft-fail); primitive binary count + drops (`6/25` on full). Fix: copy every sibling dir the crate ships. +- **Ubuntu 24.04 rustc is 1.75** — too old for `edition = "2024"`. + Dockerfile installs rustup stable; `apt install rustc` is NOT enough. +- **Apple Silicon hosts**: image builds linux/arm64 natively; binaries + produced inside won't run on x86_64 hosts. diff --git a/tests/battle/battle-entry.sh b/tests/battle/battle-entry.sh new file mode 100755 index 0000000..8ab79f5 --- /dev/null +++ b/tests/battle/battle-entry.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# tests/battle/battle-entry.sh — container ENTRYPOINT. +# Picks profile from $PROFILE env (default: minimal), runs installer, then +# verify.sh. Kept as a dedicated file (instead of a Dockerfile heredoc) so +# BuildKit isn't required and the script is editable post-image-build. +set -u + +PROFILE="${PROFILE:-minimal}" +echo "=== battle-test: profile=$PROFILE ===" +echo "=== host: $(uname -a) ===" +echo "=== cargo: $(cargo --version) ===" +echo "=== jq: $(jq --version) ===" +echo + +cd /opt/keiseikit || { echo "kit missing at /opt/keiseikit"; exit 2; } +./install.sh --profile="$PROFILE" --yes 2>&1 +INSTALL_EXIT=$? +echo +echo "=== install exit code: $INSTALL_EXIT ===" + +if [ "$INSTALL_EXIT" -ne 0 ]; then + echo "=== install failed; skipping verify ===" + exit "$INSTALL_EXIT" +fi + +echo +echo "=== running verify.sh ===" +/usr/local/bin/verify.sh diff --git a/tests/battle/verify.sh b/tests/battle/verify.sh new file mode 100755 index 0000000..c88c9ea --- /dev/null +++ b/tests/battle/verify.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh +# tests/battle/verify.sh — post-install assertions for the battle test. +# POSIX sh; runs inside ubuntu:24.04 container as root ($HOME=/root). +# Thresholds match v0.21 kit snapshot (2026-04-22): +# _blocks >= 79, skills >= 39, top hooks >= 10, _lib hooks >= 2. +set -u + +fail() { printf 'FAIL: %s\n' "$1" >&2; exit 1; } +pass() { printf 'PASS: %s\n' "$1"; } + +AG="$HOME/.claude/agents" +HK="$HOME/.claude/hooks" +SK="$HOME/.claude/skills" + +n_blocks=$(ls -1 "$AG/_blocks" 2>/dev/null | wc -l | tr -d ' ') +[ "$n_blocks" -ge 79 ] || fail "_blocks count $n_blocks < 79" +pass "_blocks count = $n_blocks (>= 79)" + +n_skills=$(ls -1 "$SK" 2>/dev/null | wc -l | tr -d ' ') +[ "$n_skills" -ge 39 ] || fail "skills count $n_skills < 39" +pass "skills count = $n_skills (>= 39)" + +n_hooks=$(find "$HK" -maxdepth 1 -type f -name '*.sh' 2>/dev/null | wc -l | tr -d ' ') +[ "$n_hooks" -ge 10 ] || fail "top hooks count $n_hooks < 10" +pass "top hooks count = $n_hooks (>= 10)" + +n_lib=$(find "$HK/_lib" -maxdepth 1 -type f -name '*.sh' 2>/dev/null | wc -l | tr -d ' ') +[ "$n_lib" -ge 2 ] || fail "_lib hooks count $n_lib < 2" +pass "_lib hooks count = $n_lib (>= 2)" + +if [ -x "$HK/_lib/test-gate.sh" ]; then + if bash "$HK/_lib/test-gate.sh" >/tmp/test-gate.out 2>&1; then + pass "test-gate.sh exits 0" + else + cat /tmp/test-gate.out >&2 + fail "test-gate.sh exited non-zero" + fi +else + fail "test-gate.sh missing at $HK/_lib/test-gate.sh" +fi + +if [ -f "$HOME/.claude/settings.json" ]; then + jq . "$HOME/.claude/settings.json" >/dev/null 2>&1 \ + && pass "settings.json parses as valid JSON" \ + || fail "settings.json is not valid JSON" +else + pass "settings.json absent (not activated — OK)" +fi + +echo "=== verify.sh: all checks passed ==="