fix(qa): run the container tests on a writable copy and add node to the test image
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
456b64f5b7
commit
69c33526f4
@ -7,7 +7,7 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- **QA: Python tests in a throwaway container** (`npm run qa -- --only pyunit --container`): runs the unit tests on production's Python 3.11 in a no-network, read-only container built from the app image (`docker/Dockerfile.test`, adds only `httpx2`); opt-in, SKIPPED when docker or the image is missing.
|
- **QA: Python tests in a throwaway container** (`npm run qa -- --only pyunit --container`): runs the unit tests on production's Python 3.11 in a no-network, read-only container built from the app image on a writable throwaway copy of the read-only mounted repo (`docker/Dockerfile.test` adds only `httpx2` and `nodejs`); opt-in, SKIPPED when docker or the image is missing.
|
||||||
- **Sonar triage `--drain`:** works through open agent issues one at a time, fires the multica webhook, waits for the agent's answer on GitHub and resends once if nothing arrives; stops on a stalled issue.
|
- **Sonar triage `--drain`:** works through open agent issues one at a time, fires the multica webhook, waits for the agent's answer on GitHub and resends once if nothing arrives; stops on a stalled issue.
|
||||||
- **Sonar triage can fire a multica webhook autopilot** after each GitHub issue (URL from `~/.config/multica-webhook` or `MULTICA_WEBHOOK_URL`, passed to curl on stdin only); the multica CLI stays as fallback.
|
- **Sonar triage can fire a multica webhook autopilot** after each GitHub issue (URL from `~/.config/multica-webhook` or `MULTICA_WEBHOOK_URL`, passed to curl on stdin only); the multica CLI stays as fallback.
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@ -62,9 +62,8 @@ Why: production runs Python 3.11 with the dependencies installed from `requireme
|
|||||||
|
|
||||||
Setup (once, local only, nothing pushed): `docker build -f docker/Dockerfile.test -t tts-voice-creator-test:local docker`. `docker/Dockerfile.test` is `FROM` the app image plus `httpx2` (needed by `starlette.testclient`, not shipped in production). Without docker or the image the stage reports SKIPPED with the build command.
|
Setup (once, local only, nothing pushed): `docker build -f docker/Dockerfile.test -t tts-voice-creator-test:local docker`. `docker/Dockerfile.test` is `FROM` the app image plus `httpx2` (needed by `starlette.testclient`, not shipped in production). Without docker or the image the stage reports SKIPPED with the build command.
|
||||||
|
|
||||||
Container: `docker run --rm --network none --read-only --tmpfs /tmp:rw,exec --tmpfs /home/app:rw`, `HOME`/`CONFIG_DIR`/`TMPDIR` under `/tmp`, repo mounted read-only at `/work`, user `app`, entrypoint `python3`.
|
Container: `docker run --rm --network none --read-only --tmpfs /tmp:rw,exec --tmpfs /home/app:rw`, `HOME`/`CONFIG_DIR`/`TMPDIR` under `/tmp`, user `app`. The repo is mounted read-only at `/src`; the tests run in a writable throwaway copy at `/tmp/work` (tar copy without `.git`, `node_modules`, `config`, `exports`, `logs`, `attachments`, `.multica`, the nested project dir; `config/regression_snapshots` is added back because a test reads it). Some tests write to the tree (e.g. `os.utime`), so the original is never written.
|
||||||
|
|
||||||
Known differences from the host run (1086 tests, all OK on host; container: 2 errors, 3 skipped):
|
The test image also has `nodejs` (Debian package, aarch64) for the browser/server fingerprint test, and `httpx2`.
|
||||||
- `test_asset_versions...build_id_changes_when_a_script_changes`: the test calls `os.utime` on a repo file; fails on the read-only mount (the mount is deliberate).
|
|
||||||
- `test_character_identity...browser_and_server_compute_identical_fingerprints`: needs `node`, which the app image does not contain.
|
Result: 1086 tests, OK, 3 skipped in the container (host: 1086 OK). Skips: `test_guided_installer.RunCommandTest` (3 tests), reason 'tkinter unavailable' (the image has no tkinter). No Python 3.11 vs 3.12 behaviour differences found. Coverage (`--sonar`) still uses the host interpreter.
|
||||||
Neither is a Python 3.11 vs 3.12 behaviour difference; the other tests pass on 3.11. Coverage (`--sonar`) still uses the host interpreter.
|
|
||||||
|
|||||||
@ -4,5 +4,7 @@
|
|||||||
FROM tts-voice-creator-clone-and-design-2:latest
|
FROM tts-voice-creator-clone-and-design-2:latest
|
||||||
USER root
|
USER root
|
||||||
# starlette.testclient (fastapi TestClient) needs an HTTP client package that production does not ship.
|
# starlette.testclient (fastapi TestClient) needs an HTTP client package that production does not ship.
|
||||||
|
# node: the browser/server fingerprint test shells out to it (distro package, matches the image's arch).
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends nodejs && rm -rf /var/lib/apt/lists/*
|
||||||
RUN pip install --no-cache-dir httpx2
|
RUN pip install --no-cache-dir httpx2
|
||||||
USER app
|
USER app
|
||||||
|
|||||||
@ -35,7 +35,9 @@ const PY = (() => {
|
|||||||
const TEST_IMAGE = 'tts-voice-creator-test:local';
|
const TEST_IMAGE = 'tts-voice-creator-test:local';
|
||||||
const containerArgs = (...cmd) => ['run', '--rm', '--network', 'none', '--read-only', '--tmpfs', '/tmp:rw,exec', '--tmpfs', '/home/app:rw',
|
const containerArgs = (...cmd) => ['run', '--rm', '--network', 'none', '--read-only', '--tmpfs', '/tmp:rw,exec', '--tmpfs', '/home/app:rw',
|
||||||
'-e', 'HOME=/tmp/home', '-e', 'CONFIG_DIR=/tmp/cfg', '-e', 'TMPDIR=/tmp', '-e', 'PYTHONDONTWRITEBYTECODE=1',
|
'-e', 'HOME=/tmp/home', '-e', 'CONFIG_DIR=/tmp/cfg', '-e', 'TMPDIR=/tmp', '-e', 'PYTHONDONTWRITEBYTECODE=1',
|
||||||
'-v', `${root}:/work:ro`, '-w', '/work', '--entrypoint', 'python3', TEST_IMAGE, ...cmd];
|
'-v', `${root}:/src:ro`, '--entrypoint', 'sh', TEST_IMAGE, '-c', COPY_AND_RUN, 'sh', ...cmd];
|
||||||
|
// Repo is mounted read-only at /src; tests run in a writable throwaway copy (some tests touch files) without the big/irrelevant dirs.
|
||||||
|
const COPY_AND_RUN = 'mkdir /tmp/work && cd /src && tar -c --exclude=./node_modules --exclude=./.git --exclude=./tts-voice-creator-clone-and-design-2 --exclude=./.multica --exclude=./exports --exclude=./logs --exclude=./config --exclude=./test-results --exclude=./.scannerwork --exclude=./attachments . | tar -x -C /tmp/work && mkdir -p /tmp/work/config && cp -a /src/config/regression_snapshots /tmp/work/config/ && cd /tmp/work && exec python3 "$@"';
|
||||||
const containerReady = () => {
|
const containerReady = () => {
|
||||||
if (!have('docker')) return 'docker not available';
|
if (!have('docker')) return 'docker not available';
|
||||||
if (sh('docker', ['image', 'inspect', TEST_IMAGE]).status) return `image ${TEST_IMAGE} missing; build: docker build -f docker/Dockerfile.test -t ${TEST_IMAGE} docker`;
|
if (sh('docker', ['image', 'inspect', TEST_IMAGE]).status) return `image ${TEST_IMAGE} missing; build: docker build -f docker/Dockerfile.test -t ${TEST_IMAGE} docker`;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user