From c7e8ea02a5ca41741bc0869e0600f0b722485862 Mon Sep 17 00:00:00 2001 From: Tobi Lutke Date: Fri, 20 Feb 2026 23:33:42 -0400 Subject: [PATCH] test: restructure container smoke tests for interactive use Replaces the inner test script with an outer driver that runs individual podman/docker commands against a pre-built image. Tests sqlite-vec loading and store unit tests under both node and bun runtimes. Supports --build (image only), --shell (interactive), and -- CMD (arbitrary command) for debugging install issues in isolation. --- test/Containerfile | 16 +++- test/smoke-install-test.sh | 58 ------------- test/smoke-install.sh | 167 +++++++++++++++++++++++++++++++++---- 3 files changed, 161 insertions(+), 80 deletions(-) delete mode 100755 test/smoke-install-test.sh diff --git a/test/Containerfile b/test/Containerfile index ac1b48f..28f32d0 100644 --- a/test/Containerfile +++ b/test/Containerfile @@ -13,9 +13,17 @@ ENV PATH="/root/.local/bin:$PATH" # Pre-install node and bun RUN mise use -g node@latest bun@latest -# Copy the packed tarball and test script +# Copy the packed tarball and install via both package managers COPY tobilu-qmd-*.tgz /tmp/ -COPY smoke-install-test.sh /tmp/ -RUN chmod +x /tmp/smoke-install-test.sh +RUN mise exec node@latest -- npm install -g /tmp/tobilu-qmd-*.tgz +RUN mise exec bun@latest -- bun install -g /tmp/tobilu-qmd-*.tgz -CMD ["/tmp/smoke-install-test.sh"] +# Copy test project (src + test + configs) and install deps +COPY test-src/ /opt/qmd/ +RUN cd /opt/qmd && mise exec node@latest -- npm install 2>/dev/null +RUN cd /opt/qmd && mise exec bun@latest -- bun install 2>/dev/null || true + +# Put everything on PATH +ENV PATH="/root/.bun/bin:/root/.local/share/mise/shims:/root/.local/bin:$PATH" + +CMD ["bash"] diff --git a/test/smoke-install-test.sh b/test/smoke-install-test.sh deleted file mode 100755 index f7e7ac6..0000000 --- a/test/smoke-install-test.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -# Smoke test: install @tobilu/qmd from tarball and verify it runs under node and bun. -# Both runtimes need node on PATH (the bin uses #!/usr/bin/env node shebang). -set -uo pipefail - -TARBALL=$(ls /tmp/tobilu-qmd-*.tgz | head -1) -PASS=0 -FAIL=0 -TMP=$(mktemp) - -ok() { printf " %-44s OK\n" "$1"; PASS=$((PASS + 1)); } -fail() { printf " %-44s FAIL\n" "$1"; FAIL=$((FAIL + 1)); cat "$TMP" | sed 's/^/ /'; } - -NODE_BIN="$(mise where node@latest)/bin" -BUN_BIN="$(mise where bun@latest)/bin" -BASE_PATH="/root/.local/bin:/usr/local/bin:/usr/bin:/bin" - -# --------------------------------------------------------------------------- -# Node: install via npm, runs with node (via shebang) -# --------------------------------------------------------------------------- -echo "=== Node $($NODE_BIN/node --version) ===" -export PATH="$NODE_BIN:$BASE_PATH" - -if npm install -g "$TARBALL" >"$TMP" 2>&1; then ok "npm install -g" -else fail "npm install -g"; fi - -timeout 10 qmd >"$TMP" 2>&1 || true -if grep -q "Usage:" "$TMP"; then ok "qmd shows help" -else fail "qmd shows help"; fi - -if timeout 10 qmd collection list >"$TMP" 2>&1; then ok "qmd collection list" -else fail "qmd collection list"; fi - -# --------------------------------------------------------------------------- -# Bun: install via bun, still runs with node (shebang) -# --------------------------------------------------------------------------- -echo "" -echo "=== Bun $($BUN_BIN/bun --version) ===" -export PATH="$BUN_BIN:$HOME/.bun/bin:$NODE_BIN:$BASE_PATH" - -if bun install -g "$TARBALL" >"$TMP" 2>&1; then ok "bun install -g" -else fail "bun install -g"; fi - -timeout 10 "$HOME/.bun/bin/qmd" >"$TMP" 2>&1 || true -if grep -q "Usage:" "$TMP"; then ok "qmd shows help (bun-installed)" -else fail "qmd shows help (bun-installed)"; fi - -if timeout 10 "$HOME/.bun/bin/qmd" collection list >"$TMP" 2>&1; then ok "qmd collection list (bun-installed)" -else fail "qmd collection list (bun-installed)"; fi - -rm -f "$TMP" - -# --------------------------------------------------------------------------- -# Summary -# --------------------------------------------------------------------------- -echo "" -echo "=== Results: $PASS passed, $FAIL failed ===" -[[ $FAIL -eq 0 ]] diff --git a/test/smoke-install.sh b/test/smoke-install.sh index 82f31a6..9acf2b7 100755 --- a/test/smoke-install.sh +++ b/test/smoke-install.sh @@ -1,6 +1,12 @@ #!/usr/bin/env bash -# Build, pack, and smoke-test qmd in a container with mise + node + bun. +# Build a container image with qmd installed via npm and bun, then run smoke tests. # Works with docker or podman (whichever is available). +# +# Usage: +# test/smoke-install.sh # build + run all smoke tests +# test/smoke-install.sh --build # build image only +# test/smoke-install.sh --shell # drop into container shell +# test/smoke-install.sh -- CMD... # run arbitrary command in container set -euo pipefail cd "$(dirname "$0")/.." @@ -14,25 +20,150 @@ else echo "Error: neither podman nor docker found" >&2 exit 1 fi -echo "Using: $CTR" -# Build TypeScript -echo "==> Building TypeScript..." -npm run build --silent +IMAGE=qmd-smoke -# Pack tarball into test/ (the build context) -echo "==> Packing tarball..." -rm -f test/tobilu-qmd-*.tgz -TARBALL=$(npm pack --pack-destination test/ 2>/dev/null | tail -1) -echo " $TARBALL" +build_image() { + echo "==> Building TypeScript..." + npm run build --silent -# Build container image -echo "==> Building container..." -$CTR build -f test/Containerfile -t qmd-smoke test/ + echo "==> Packing tarball..." + rm -f test/tobilu-qmd-*.tgz + TARBALL=$(npm pack --pack-destination test/ 2>/dev/null | tail -1) + echo " $TARBALL" -# Run smoke tests -echo "==> Running smoke tests..." -$CTR run --rm qmd-smoke + # Copy project files into build context so vitest/bun tests can run inside + rm -rf test/test-src + mkdir -p test/test-src/src test/test-src/test + cp src/*.ts test/test-src/src/ + cp -r dist test/test-src/ + cp test/*.test.ts test/test-src/test/ + cp package.json tsconfig.json tsconfig.build.json test/test-src/ -# Clean up tarball -rm -f test/tobilu-qmd-*.tgz + echo "==> Building container image ($CTR)..." + $CTR build -f test/Containerfile -t "$IMAGE" test/ + + # Clean up + rm -f test/tobilu-qmd-*.tgz + rm -rf test/test-src + echo "==> Image ready: $IMAGE" +} + +run() { + $CTR run --rm "$IMAGE" bash -c "$*" +} + +PASS=0 +FAIL=0 + +ok() { printf " %-50s OK\n" "$1"; PASS=$((PASS + 1)); } +fail() { printf " %-50s FAIL\n" "$1"; FAIL=$((FAIL + 1)); echo "$2" | sed 's/^/ /'; } + +smoke_test() { + local label="$1"; shift + local out + if out=$(run "$@" 2>&1); then + ok "$label" + else + fail "$label" "$out" + fi +} + +smoke_test_output() { + local label="$1"; local expect="$2"; shift 2 + local out + out=$(run "$@" 2>&1) || true + if echo "$out" | grep -q "$expect"; then + ok "$label" + else + fail "$label" "$out" + fi +} + +run_smoke_tests() { + # ------------------------------------------------------------------ + # Node (npm-installed qmd) + # ------------------------------------------------------------------ + local NODE_BIN='$(mise where node@latest)/bin' + echo "=== Node (npm install) ===" + + smoke_test_output "qmd shows help" "Usage:" \ + "export PATH=$NODE_BIN:\$PATH; qmd" + + smoke_test "qmd collection list" \ + "export PATH=$NODE_BIN:\$PATH; qmd collection list" + + smoke_test "qmd status" \ + "export PATH=$NODE_BIN:\$PATH; qmd status" + + smoke_test "sqlite-vec loads" \ + "export PATH=$NODE_BIN:\$PATH; + NPM_GLOBAL=\$(npm root -g); + node -e \" + const {openDatabase, loadSqliteVec} = await import('\$NPM_GLOBAL/@tobilu/qmd/dist/db.js'); + const db = openDatabase(':memory:'); + loadSqliteVec(db); + const r = db.prepare('SELECT vec_version() as v').get(); + console.log('sqlite-vec', r.v); + if (!r.v) process.exit(1); + \"" + + smoke_test "vitest (node)" \ + "export PATH=$NODE_BIN:\$PATH; cd /opt/qmd && npx vitest run --reporter=verbose test/store.test.ts 2>&1 | tail -5" + + # ------------------------------------------------------------------ + # Bun (bun-installed qmd) + # ------------------------------------------------------------------ + local BUN_BIN='$(mise where bun@latest)/bin' + echo "" + echo "=== Bun (bun install) ===" + + smoke_test_output "qmd shows help" "Usage:" \ + "export PATH=$BUN_BIN:$NODE_BIN:\$PATH; \$HOME/.bun/bin/qmd" + + smoke_test "qmd collection list" \ + "export PATH=$BUN_BIN:$NODE_BIN:\$PATH; \$HOME/.bun/bin/qmd collection list" + + smoke_test "qmd status" \ + "export PATH=$BUN_BIN:$NODE_BIN:\$PATH; \$HOME/.bun/bin/qmd status" + + smoke_test "sqlite-vec loads (bun)" \ + "export PATH=$BUN_BIN:\$PATH; bun -e \" + const {openDatabase, loadSqliteVec} = await import('\$HOME/.bun/install/global/node_modules/@tobilu/qmd/dist/db.js'); + const db = openDatabase(':memory:'); + loadSqliteVec(db); + const r = db.prepare('SELECT vec_version() as v').get(); + console.log('sqlite-vec', r.v); + if (!r.v) process.exit(1); + \"" + + smoke_test "bun test store" \ + "export PATH=$BUN_BIN:\$PATH; cd /opt/qmd && bun test --preload ./src/test-preload.ts --timeout 30000 test/store.test.ts 2>&1 | tail -10" + + # ------------------------------------------------------------------ + echo "" + echo "=== Results: $PASS passed, $FAIL failed ===" + [[ $FAIL -eq 0 ]] +} + +# Parse arguments +case "${1:-}" in + --build) + build_image + ;; + --shell) + build_image + echo "==> Dropping into container shell..." + $CTR run --rm -it "$IMAGE" bash + ;; + --) + shift + run "$@" + ;; + *) + build_image + echo "" + echo "==> Running smoke tests..." + run_smoke_tests + ;; +esac