Skip to content

Commit 79c34c5

Browse files
kulvirgitclaude
andauthored
feat: add local E2E sanity test harness (#461)
Docker-based "new user simulator" that tests the shipped npm artifact: - Phase 1: verify-install (9 checks: binary, skills, napi, dbt, git) - Phase 2: smoke-tests (10 E2E tests via altimate run, parallelized) - Phase 3: resilience (8 tests: SQLite, WAL, sessions, compaction, config) - PR-aware test generation (git diff → targeted tests) - Local CI pipeline (bun run ci → typecheck + tests + markers) - Machine-aware parallelism (2-6 concurrent based on cores/RAM) 27 tests, all passing in ~2:48 on 20-core machine. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ad99c5c commit 79c34c5

23 files changed

+1266
-1
lines changed

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
"prepare": "husky",
1515
"random": "echo 'Random script'",
1616
"hello": "echo 'Hello World!'",
17-
"test": "echo 'do not run tests from root' && exit 1"
17+
"test": "echo 'do not run tests from root' && exit 1",
18+
"ci": "test/sanity/ci-local.sh",
19+
"ci:full": "test/sanity/ci-local.sh full",
20+
"ci:pr": "test/sanity/ci-local.sh pr",
21+
"sanity": "docker compose -f test/sanity/docker-compose.yml up --build --abort-on-container-exit --exit-code-from sanity",
22+
"sanity:upgrade": "docker compose -f test/sanity/docker-compose.yml -f test/sanity/docker-compose.upgrade.yml up --build --abort-on-container-exit --exit-code-from sanity"
1823
},
1924
"workspaces": {
2025
"packages": [

test/sanity/.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Required for smoke tests (LLM-dependent)
2+
# WARNING: Never commit real credentials. Use secrets management for production.
3+
ANTHROPIC_API_KEY=your-api-key-here
4+
5+
# Optional: Snowflake credentials for cloud warehouse testing
6+
# ALTIMATE_CODE_CONN_SNOWFLAKE_TEST='{"account":"...","user":"...","password":"...","warehouse":"...","database":"..."}'

test/sanity/Dockerfile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
FROM oven/bun:1.3.10-debian
2+
3+
# System deps (what a real user would have)
4+
RUN apt-get update && apt-get install -y \
5+
git python3 python3-pip python3-venv curl sqlite3 \
6+
&& rm -rf /var/lib/apt/lists/*
7+
8+
# dbt in venv (matches real user setup)
9+
RUN python3 -m venv /opt/dbt && \
10+
/opt/dbt/bin/pip install --quiet dbt-core dbt-duckdb dbt-postgres
11+
ENV PATH="/opt/dbt/bin:$PATH"
12+
13+
# Fresh user, clean HOME — simulates new user
14+
ENV HOME=/home/testuser
15+
RUN useradd -m testuser
16+
USER testuser
17+
WORKDIR /home/testuser
18+
19+
# Copy the built binary directly (simulates what postinstall does)
20+
# The real npm publish pipeline rewrites workspace:* deps — we can't use npm pack directly.
21+
# Instead, copy the built binary + run postinstall manually.
22+
COPY --chown=testuser packages/opencode/dist/@altimateai/ /home/testuser/.altimate-install/dist/@altimateai/
23+
COPY --chown=testuser packages/opencode/script/postinstall.mjs /home/testuser/.altimate-install/postinstall.mjs
24+
COPY --chown=testuser packages/opencode/package.json /home/testuser/.altimate-install/package.json
25+
COPY --chown=testuser .opencode/skills/ /home/testuser/.altimate-install/skills/
26+
27+
# Install altimate-core native binding (required at runtime)
28+
RUN cd /home/testuser/.altimate-install && \
29+
echo '{"dependencies":{"@altimateai/altimate-core":"latest"}}' > package.json && \
30+
bun install && \
31+
node -e "require('@altimateai/altimate-core')" 2>/dev/null || { echo "FATAL: altimate-core install failed"; exit 1; }
32+
33+
# Link binary to PATH and copy skills to ~/.altimate/builtin/
34+
# Detect architecture: use TARGETARCH from docker buildx, fall back to uname
35+
ARG TARGETARCH
36+
RUN ARCH="${TARGETARCH:-$(uname -m | sed 's/x86_64/x64/' | sed 's/aarch64/arm64/')}" && \
37+
mkdir -p /home/testuser/.local/bin && \
38+
cp /home/testuser/.altimate-install/dist/@altimateai/altimate-code-linux-${ARCH}/bin/altimate /home/testuser/.local/bin/altimate && \
39+
chmod +x /home/testuser/.local/bin/altimate && \
40+
mkdir -p /home/testuser/.altimate/builtin && \
41+
cp -r /home/testuser/.altimate-install/skills/* /home/testuser/.altimate/builtin/
42+
ENV PATH="/home/testuser/.local/bin:$PATH"
43+
ENV NODE_PATH="/home/testuser/.altimate-install/node_modules"
44+
45+
# Copy test scripts
46+
COPY --chown=testuser test/sanity/ /home/testuser/sanity/
47+
RUN chmod +x /home/testuser/sanity/run.sh \
48+
/home/testuser/sanity/phases/*.sh \
49+
/home/testuser/sanity/pr-tests/*.sh \
50+
/home/testuser/sanity/lib/*.sh
51+
52+
ENTRYPOINT ["/home/testuser/sanity/run.sh"]

test/sanity/Dockerfile.upgrade

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
FROM oven/bun:1.3.10-debian
2+
3+
RUN apt-get update && apt-get install -y \
4+
git python3 python3-pip python3-venv curl sqlite3 \
5+
&& rm -rf /var/lib/apt/lists/*
6+
7+
RUN python3 -m venv /opt/dbt && \
8+
/opt/dbt/bin/pip install --quiet dbt-core dbt-duckdb dbt-postgres
9+
ENV PATH="/opt/dbt/bin:$PATH"
10+
11+
ENV HOME=/home/testuser
12+
RUN useradd -m testuser
13+
USER testuser
14+
WORKDIR /home/testuser
15+
16+
# Install previous version first
17+
ARG PRIOR_VERSION=latest
18+
RUN npm install -g @altimateai/altimate-code@${PRIOR_VERSION}
19+
20+
# Record old version for comparison
21+
RUN altimate --version > /tmp/old-version.txt 2>/dev/null || echo "unknown" > /tmp/old-version.txt
22+
23+
# Run once to seed DB, config, session data
24+
RUN git init /tmp/seed-project && \
25+
cd /tmp/seed-project && \
26+
git config user.name "seed" && \
27+
git config user.email "seed@test.local" && \
28+
echo '{}' > package.json && \
29+
git add -A && git commit -q -m "seed"
30+
31+
# Upgrade to new version
32+
COPY --chown=testuser dist/package.tgz /tmp/altimate-code.tgz
33+
RUN npm install -g /tmp/altimate-code.tgz
34+
35+
# Copy test scripts
36+
COPY --chown=testuser test/sanity/ /home/testuser/sanity/
37+
RUN chmod +x /home/testuser/sanity/run.sh \
38+
/home/testuser/sanity/phases/*.sh \
39+
/home/testuser/sanity/pr-tests/*.sh \
40+
/home/testuser/sanity/lib/*.sh
41+
42+
ENV OLD_VERSION_FILE=/tmp/old-version.txt
43+
44+
ENTRYPOINT ["/home/testuser/sanity/run.sh", "--upgrade"]

test/sanity/ci-local.sh

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/bin/bash
2+
# Local CI pipeline — ports .github/workflows/ci.yml to run locally
3+
set -euo pipefail
4+
5+
MODE="${1:-fast}"
6+
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
7+
EXIT_CODE=0
8+
9+
# Ensure Docker containers are cleaned up on exit (full/pr modes)
10+
cleanup_docker() {
11+
docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" down --volumes --remove-orphans 2>/dev/null || true
12+
}
13+
trap cleanup_docker EXIT
14+
15+
echo "========================================"
16+
echo " Local CI Pipeline — mode: $MODE"
17+
echo " Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
18+
echo "========================================"
19+
20+
run_step() {
21+
local name="$1"; shift
22+
echo ""
23+
echo "--- $name ---"
24+
if "$@"; then
25+
echo " >>> $name: PASSED"
26+
else
27+
echo " >>> $name: FAILED"
28+
EXIT_CODE=1
29+
fi
30+
}
31+
32+
# ── Fast mode (default — what pre-push hook runs) ──────────────
33+
34+
echo ""
35+
echo "=== Fast CI ==="
36+
37+
run_step "Typecheck" bun turbo typecheck
38+
39+
run_step "Unit Tests (opencode)" bash -c "cd $REPO_ROOT/packages/opencode && bun test --timeout 30000"
40+
41+
run_step "Unit Tests (dbt-tools)" bash -c "cd $REPO_ROOT/packages/dbt-tools && bun run test"
42+
43+
# Marker guard (needs upstream remote)
44+
if git remote | grep -q upstream; then
45+
run_step "Marker Guard" bun run "$REPO_ROOT/script/upstream/analyze.ts" --markers --base origin/main --strict
46+
else
47+
echo ""
48+
echo "--- Marker Guard ---"
49+
echo " SKIP: upstream remote not configured"
50+
fi
51+
52+
# ── Full mode ──────────────────────────────────────────────────
53+
54+
if [ "$MODE" = "--full" ] || [ "$MODE" = "full" ]; then
55+
echo ""
56+
echo "=== Full CI (Docker) ==="
57+
58+
# Driver E2E with Docker containers
59+
run_step "Docker Services Up" docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" up -d postgres mysql mssql redshift
60+
61+
echo " Waiting for services to be healthy..."
62+
HEALTHY=0
63+
for _wait in $(seq 1 30); do
64+
HEALTHY=$(docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" ps --format json 2>/dev/null | grep -c '"healthy"' || echo "0")
65+
if [ "$HEALTHY" -ge 4 ]; then break; fi
66+
sleep 2
67+
done
68+
69+
if [ "$HEALTHY" -lt 4 ]; then
70+
echo " >>> Docker Services: FAILED ($HEALTHY/4 healthy after 60s)"
71+
EXIT_CODE=1
72+
else
73+
echo " >>> Docker Services: $HEALTHY/4 healthy"
74+
fi
75+
76+
# Skip driver tests if services aren't healthy
77+
if [ "$HEALTHY" -lt 4 ]; then
78+
echo " SKIP: Driver E2E tests (services not healthy)"
79+
else
80+
81+
run_step "Driver E2E (local)" bash -c "cd $REPO_ROOT/packages/opencode && \
82+
TEST_PG_HOST=127.0.0.1 TEST_PG_PORT=15432 TEST_PG_PASSWORD=testpass123 \
83+
bun test test/altimate/drivers-e2e.test.ts --timeout 30000"
84+
85+
run_step "Driver E2E (docker)" bash -c "cd $REPO_ROOT/packages/opencode && \
86+
TEST_MYSQL_HOST=127.0.0.1 TEST_MYSQL_PORT=13306 TEST_MYSQL_PASSWORD=testpass123 \
87+
TEST_MSSQL_HOST=127.0.0.1 TEST_MSSQL_PORT=11433 TEST_MSSQL_PASSWORD='TestPass123!' \
88+
TEST_REDSHIFT_HOST=127.0.0.1 TEST_REDSHIFT_PORT=15439 TEST_REDSHIFT_PASSWORD=testpass123 \
89+
bun test test/altimate/drivers-docker-e2e.test.ts --timeout 30000"
90+
91+
# Full sanity suite in Docker
92+
run_step "Sanity Suite (Docker)" docker compose -f "$REPO_ROOT/test/sanity/docker-compose.yml" \
93+
up --build --abort-on-container-exit --exit-code-from sanity
94+
95+
fi # end healthy gate
96+
fi
97+
98+
# ── PR mode ────────────────────────────────────────────────────
99+
100+
if [ "$MODE" = "--pr" ] || [ "$MODE" = "pr" ]; then
101+
echo ""
102+
echo "=== PR-Aware Tests ==="
103+
104+
run_step "Generate PR tests" bash "$REPO_ROOT/test/sanity/pr-tests/generate.sh" origin/main
105+
run_step "Run PR tests" bash "$REPO_ROOT/test/sanity/pr-tests/run-pr-tests.sh"
106+
fi
107+
108+
# ── Summary ────────────────────────────────────────────────────
109+
110+
echo ""
111+
echo "========================================"
112+
if [ $EXIT_CODE -eq 0 ]; then
113+
echo " LOCAL CI: ALL PASSED"
114+
else
115+
echo " LOCAL CI: SOME STEPS FAILED"
116+
fi
117+
echo "========================================"
118+
119+
exit $EXIT_CODE
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Override for upgrade testing — replaces sanity service with Dockerfile.upgrade
2+
services:
3+
sanity:
4+
build:
5+
context: ../..
6+
dockerfile: test/sanity/Dockerfile.upgrade
7+
args:
8+
PRIOR_VERSION: ${PRIOR_VERSION:-latest}

test/sanity/docker-compose.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
services:
2+
sanity:
3+
build:
4+
context: ../..
5+
dockerfile: test/sanity/Dockerfile
6+
environment:
7+
- ANTHROPIC_API_KEY
8+
- ALTIMATE_CODE_CONN_SNOWFLAKE_TEST
9+
- TEST_PG_HOST=postgres
10+
- TEST_PG_PORT=5432
11+
- TEST_PG_PASSWORD=testpass123
12+
- TEST_MYSQL_HOST=mysql
13+
- TEST_MYSQL_PORT=3306
14+
- TEST_MYSQL_PASSWORD=testpass123
15+
- TEST_MSSQL_HOST=mssql
16+
- TEST_MSSQL_PORT=1433
17+
- TEST_MSSQL_PASSWORD=TestPass123!
18+
- TEST_REDSHIFT_HOST=redshift
19+
- TEST_REDSHIFT_PORT=5432
20+
- TEST_REDSHIFT_PASSWORD=testpass123
21+
depends_on:
22+
postgres:
23+
condition: service_healthy
24+
mysql:
25+
condition: service_healthy
26+
mssql:
27+
condition: service_healthy
28+
redshift:
29+
condition: service_healthy
30+
31+
postgres:
32+
image: postgres:16-alpine
33+
environment:
34+
POSTGRES_PASSWORD: testpass123
35+
ports:
36+
- "15432:5432"
37+
healthcheck:
38+
test: pg_isready
39+
interval: 5s
40+
retries: 10
41+
42+
mysql:
43+
image: mysql:8.0
44+
environment:
45+
MYSQL_ROOT_PASSWORD: testpass123
46+
MYSQL_DATABASE: testdb
47+
ports:
48+
- "13306:3306"
49+
healthcheck:
50+
test: mysqladmin ping -h 127.0.0.1
51+
interval: 5s
52+
retries: 20
53+
54+
mssql:
55+
image: mcr.microsoft.com/azure-sql-edge:latest
56+
environment:
57+
ACCEPT_EULA: "Y"
58+
MSSQL_SA_PASSWORD: "TestPass123!"
59+
ports:
60+
- "11433:1433"
61+
healthcheck:
62+
test: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'TestPass123!' -Q 'SELECT 1' || exit 1
63+
interval: 10s
64+
retries: 20
65+
66+
redshift:
67+
image: postgres:16-alpine
68+
environment:
69+
POSTGRES_PASSWORD: testpass123
70+
POSTGRES_DB: dev
71+
ports:
72+
- "15439:5432"
73+
healthcheck:
74+
test: pg_isready
75+
interval: 5s
76+
retries: 10
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"provider": {
3+
"anthropic": {
4+
"api_key": 12345
5+
}
6+
},
7+
"experimental": {
8+
"not_a_real_field": "should be ignored",
9+
"auto_mcp_discovery": "not-a-boolean"
10+
},
11+
"unknown_top_level": true
12+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- Seed a large session to test compaction circuit breaker
2+
-- This creates a session with enough message data to trigger isOverflow()
3+
-- Note: This is a minimal seed — actual compaction depends on token counting
4+
-- which requires the LLM provider. This just ensures the DB structure is valid.
5+
6+
INSERT OR IGNORE INTO session (id, project_id, slug, directory, title, version, time_created, time_updated)
7+
VALUES ('ses_sanity_compaction_test', 'proj_sanity', 'sanity-compaction', '/tmp', 'Compaction Test Session', 1, strftime('%s','now') * 1000, strftime('%s','now') * 1000);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"provider": {
3+
"anthropic": {
4+
"api_key": "test-key-not-real"
5+
}
6+
},
7+
"experimental": {
8+
"auto_mcp_discovery": true
9+
}
10+
}

0 commit comments

Comments
 (0)