ci: parallelize tests, isolating the slow deploy tests (~10min → ~6min) - #200
Merged
Conversation
CI's test job grew ~5min (#190) → ~10-11min as test_kayak_deploy.py's slow tests went 3 → 7 (4C PRs #192-#196). Each runs the real kayak-deploy.sh — builds a wheel + installs the 30-package prod lock into a fresh venv, ~135s, all serial. Measured warm≈cold (135s vs 142s), so it's work-bound, not download-bound (ruling out a prebuilt-wheel seam / pip pre-warm). Parallelize with pytest-xdist (-n auto), which surfaced three things, fixed here: 1. Latent hermeticity bug: _run passed {**os.environ, ...} to the deploy subprocess, so under -n a concurrent test's leaked DATASET_DIR/METADATA_DIR reached the STAGED engine's kayak.config import (a mismatch raises ValueError). _run now starts from the inherited env MINUS kayak's config vars (_KAYAK_CONFIG_ENV) — a real robustness win regardless of speed. 2. Isolation: the run is split into two -n auto passes. The slow pass is path-scoped (pytest tests/test_scripts/test_kayak_deploy.py -m slow): collecting the whole suite imports other modules whose import-time os.environ side effects leak into the staged-engine subprocess (plain `-m slow` fails the activation tests; path-scoped passes). New test_slow_tests_colocated.py guards that all slow markers live in that file, so a slow test added elsewhere fails loudly rather than silently skipping CI. 3. The slow pass runs WITHOUT --cov: pytest-cov's subprocess hooks break the activation tests under -n, and --cov=kayak can't instrument the separately-built staged venv anyway (the deploy tests add ~0 coverage — light-alone = full-run 83%). The light suite carries the 75% gate; cache: pip on the test job trims the small per-test download. Validated locally: light 1771 passed @ 83% in 61s; slow 7 passed; guard + ruff + shellcheck + both lock drift checks clean. No deployer/production code touched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Speed up CI: parallelize the suite, isolating + uninstrumenting the heavy deploy tests
CI's
testjob grew ~5 min (#190) → ~10–11 min astest_kayak_deploy.py's slow tests went 3 → 7 (the 4C deploy PRs #192–#196 added the rollback / serving-gate / quiesce activation tests). Each runs the realkayak-deploy.sh— builds an engine wheel +pip install --require-hashesthe 30-package prod lock into a fresh venv — ~135 s/test, all serial.Investigation (measured)
-n autoran 2.6× faster (366 s vs ~945 s serial), all passing.Three things, found the hard way
1. Latent test-hermeticity bug.
_runpassed{**os.environ, ...}to the deploy subprocess, so under-na concurrent test's leakedDATASET_DIR/METADATA_DIRreached the staged engine'skayak.configimport, which raisesValueErroron the mismatch. (The harness comment even hinted: "drop a dev box's leaked METADATA_DIR" — but left it per-test.)_runnow starts from a hermetic base — the inherited env minus kayak's config vars (_KAYAK_CONFIG_ENV). A real robustness win regardless of speed.2. Isolation — separate process AND path-scoped collection. The run is split into two
-n autopasses:-m 'not slow'(light) and the deploy file (slow). The slow pass ispytest tests/test_scripts/test_kayak_deploy.py -m slow— path-scoped on purpose: collecting the whole suite imports other test modules whose import-timeos.environside effects leak into the staged-engine subprocess (a plainpytest -m slowfails the activation tests; the path-scoped form passes). A new guard test (test_slow_tests_colocated.py) asserts allslowmarkers live in that file, so a future slow test elsewhere fails loudly instead of silently skipping CI.3. Coverage instrumentation breaks the activation tests. Under
--cov, pytest-cov's subprocess hooks make the activation tests (which spawn the staged engine repeatedly) fail; without--covthey pass. And--cov=kayakcan't instrument those subprocesses anyway (separately-built staged venv), so the deploy tests contribute ~0 coverage (light-alone = full-run 83%). So the light suite carries the coverage gate and the slow pass runs uninstrumented.Changes (test + CI only — no deployer/production code)
_run: scrub kayak config env from the deploy subprocess (hermeticity fix).test_slow_tests_colocated.pyguard.pytest-xdistdev dep; CI runs two-n autopasses — light (gate) + path-scoped slow (no--cov).cache: pipon the test job (minor — saves the ~7 s/test download portion).Validated locally
-n auto+ gate: 83.10% coverage (same as the full run — confirms the deploy tests add ~0), 1771 passed, 61 s.-n auto(no cov): 7 passed.uv lock --check+ both lock drift checks pass; ruff + shellcheck clean.Expected CI: ~10 min → ~5–6 min (light ~1–1.5 min ∥, slow 7 tests over 4 cores ≈ 2 rounds).
🤖 Generated with Claude Code