From 40d07254973009d368517856c39ef56d6bb268b3 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Wed, 8 Apr 2026 15:06:12 -0600 Subject: [PATCH] chore: add docker-based test environment (#13) Reproducible Python 3.12-slim container that installs the project editable with dev deps. Adds pytest-asyncio to dev deps so async tests run cleanly inside the container (host had it installed out-of-band). scripts/docker-test.sh provides build, test, ask, replay, and shell subcommands. The ask/replay/shell commands mount ~/secrets read-only and ~/.marchwarden read-write so end-to-end runs persist traces back to the host. Co-Authored-By: Claude Opus 4.6 (1M context) --- .dockerignore | 15 ++++++++++ Dockerfile | 24 ++++++++++++++++ README.md | 14 ++++++++++ pyproject.toml | 1 + scripts/docker-test.sh | 62 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 scripts/docker-test.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d87fa0d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.gitignore +.venv +docs/wiki +**/__pycache__ +**/*.pyc +**/*.pyo +*.egg-info +.pytest_cache +.mypy_cache +.ruff_cache +.coverage +htmlcov +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..19c00f6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_NO_CACHE_DIR=1 + +WORKDIR /app + +# Install build deps separately so the layer caches when source changes. +COPY pyproject.toml README.md ./ +RUN pip install --upgrade pip + +# Copy the project and install editable with dev extras. +COPY cli ./cli +COPY researchers ./researchers +COPY orchestrator ./orchestrator +COPY tests ./tests +RUN pip install -e ".[dev]" + +# Trace files land here; mount a volume to persist across runs. +RUN mkdir -p /root/.marchwarden/traces + +CMD ["pytest", "-q"] diff --git a/README.md b/README.md index 3a7e17e..b5ac669 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,20 @@ marchwarden ask "What are ideal crops for a garden in Utah?" marchwarden replay ``` +## Docker test environment + +A reproducible container is available for running the test suite and the +CLI without depending on the host's Python install: + +```bash +scripts/docker-test.sh build # build the image +scripts/docker-test.sh test # run pytest +scripts/docker-test.sh ask "question" # run `marchwarden ask` end-to-end + # (mounts ~/secrets ro and ~/.marchwarden rw) +scripts/docker-test.sh replay # replay a trace from ~/.marchwarden/traces +scripts/docker-test.sh shell # interactive bash in the container +``` + ## Documentation - **[Architecture](https://forgejo.labbity.unbiasedgeek.com/archeious/marchwarden/wiki/Architecture)** — system design, researcher contract, MCP flow diff --git a/pyproject.toml b/pyproject.toml index e463baf..68f97b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ dev = [ "pytest>=7.0", "pytest-cov>=4.0", + "pytest-asyncio>=0.21", "black>=23.0", "ruff>=0.1.0", "mypy>=1.0", diff --git a/scripts/docker-test.sh b/scripts/docker-test.sh new file mode 100755 index 0000000..e7cc707 --- /dev/null +++ b/scripts/docker-test.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Helper for the dockerized test/run environment. +# +# Usage: +# scripts/docker-test.sh build Build the image +# scripts/docker-test.sh test Run pytest in the container +# scripts/docker-test.sh ask "..." Run `marchwarden ask` end-to-end +# (mounts ~/secrets ro and ~/.marchwarden rw) +# scripts/docker-test.sh shell Drop into a bash shell in the container + +set -euo pipefail + +IMAGE="marchwarden-test" +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +cmd="${1:-test}" +shift || true + +case "$cmd" in + build) + docker build -t "$IMAGE" "$ROOT" + ;; + + test) + docker run --rm -v "$ROOT:/app" "$IMAGE" pytest -q "$@" + ;; + + ask) + if [ ! -f "$HOME/secrets" ]; then + echo "error: ~/secrets not found on host" >&2 + exit 1 + fi + mkdir -p "$HOME/.marchwarden/traces" + docker run --rm -it \ + -v "$ROOT:/app" \ + -v "$HOME/secrets:/root/secrets:ro" \ + -v "$HOME/.marchwarden:/root/.marchwarden" \ + "$IMAGE" marchwarden ask "$@" + ;; + + replay) + mkdir -p "$HOME/.marchwarden/traces" + docker run --rm \ + -v "$ROOT:/app" \ + -v "$HOME/.marchwarden:/root/.marchwarden" \ + "$IMAGE" marchwarden replay "$@" + ;; + + shell) + docker run --rm -it \ + -v "$ROOT:/app" \ + -v "$HOME/secrets:/root/secrets:ro" \ + -v "$HOME/.marchwarden:/root/.marchwarden" \ + "$IMAGE" bash + ;; + + *) + echo "unknown command: $cmd" >&2 + echo "usage: $0 {build|test|ask|replay|shell}" >&2 + exit 1 + ;; +esac