mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-03 11:58:28 +08:00
ci: parallel tests, superseded-run cancellation, a 15-minute cap, docs-only skip
The diagnosis, from run logs: the suite itself is healthy (1,757 tests, none slower than 4.5s,
average 42ms) — the pain was SERIAL execution amplified by GitHub's shared-runner lottery. One
throttled run showed 68 seconds before the first test started and 17 stalls spread evenly through
the run; nothing in our code was the bottleneck.
Four measures, each commented in the workflow where it lives:
* pytest -n auto — the suite parallelizes cleanly once each xdist worker gets its OWN sqlite
file (conftest: the first parallel attempt scored 1,022 errors from twelve workers dropping
each other's tables in one shared treg-test.db, exactly as the old comment warned). xdist
arrives via `uv run --with`, deliberately not the lockfile: it is a CI-only concern and the
team's older uv rewrites uv.lock's entire format on any touch.
* concurrency + cancel-in-progress — a new push kills the outdated run (two were overlapped the
day this landed).
* timeout-minutes: 15 — a lottery-loser run dies loudly; a re-run lands on a healthier machine.
* docs-only PUSHES skip the suite; pull requests ALWAYS run, because a skipped required check
would block merging forever.
Measured locally: serial 140s, 4 workers 74s, 12 workers 63s (a ~50s floor: per-worker startup
plus the longest chain). Projection for GitHub's 4-core runners at their measured 2.4x-per-core
slowness: about 3 minutes end to end on a healthy runner, 9-10 capped at 15 on a throttled one,
~20 seconds for a docs push. This push is the first live measurement.
This commit is contained in:
+36
-17
@@ -5,6 +5,13 @@ on:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
# A new push supersedes the running build of the same branch or PR: the outdated run is cancelled
|
||||
# instead of holding a runner and queueing the new one behind it (two overlapped runs were live
|
||||
# the day this landed).
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Least-privilege: CI only reads the repo (checkout + tests + secret scan).
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -12,28 +19,40 @@ permissions:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
# A throttled shared runner once turned the 5-minute suite into 20+ (log showed 68s before the
|
||||
# first test started). Die loudly instead of eating half an hour; a re-run usually lands on a
|
||||
# healthier machine.
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2 # the docs-only check diffs HEAD against its parent
|
||||
- name: Detect a docs-only push
|
||||
id: docs
|
||||
# Direct pushes that change only docs/ and *.md skip the suite — half of one week's runs
|
||||
# bought nothing. PULL REQUESTS ALWAYS RUN: the branch-protection check must report, and a
|
||||
# skipped required check would block the merge forever.
|
||||
run: |
|
||||
ONLY=false
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
CHANGED=$(git diff --name-only HEAD^ HEAD || true)
|
||||
if [ -n "$CHANGED" ] && ! echo "$CHANGED" | grep -qvE '^docs/|\.md$'; then
|
||||
ONLY=true
|
||||
fi
|
||||
fi
|
||||
echo "docs-only: $ONLY"
|
||||
echo "only=$ONLY" >> "$GITHUB_OUTPUT"
|
||||
- uses: astral-sh/setup-uv@v7
|
||||
if: steps.docs.outputs.only != 'true'
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Install dependencies
|
||||
if: steps.docs.outputs.only != 'true'
|
||||
run: uv sync
|
||||
- name: Run tests
|
||||
run: uv run pytest -q
|
||||
|
||||
gitleaks:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # scan the full history of the push/PR, not just the tip
|
||||
- name: Install gitleaks
|
||||
env:
|
||||
GITLEAKS_VERSION: "8.21.2"
|
||||
run: |
|
||||
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
| tar -xz gitleaks
|
||||
sudo mv gitleaks /usr/local/bin/
|
||||
- name: Scan for secrets
|
||||
run: gitleaks detect --source . --config .gitleaks.toml --verbose --redact
|
||||
if: steps.docs.outputs.only != 'true'
|
||||
# -n auto: the suite is 1,700+ small independent tests — serial execution is what let a
|
||||
# throttled runner turn minutes into tens of minutes. xdist arrives via --with rather than
|
||||
# the lockfile on purpose: it is a CI-only concern, and the team's older uv rewrites
|
||||
# uv.lock's whole format on any touch.
|
||||
run: uv run --with pytest-xdist pytest -n auto -q
|
||||
|
||||
+6
-1
@@ -14,7 +14,12 @@ import os
|
||||
# TREG_TEST_DB_URL (not TREG_DATABASE_URL — a stray production URL in a shell must never become the
|
||||
# test target) lets two suites run side by side: `reset_db()` DROPS tables, so two concurrent runs
|
||||
# against the same sqlite file tear down each other's schema mid-test.
|
||||
os.environ["TREG_DATABASE_URL"] = os.environ.get("TREG_TEST_DB_URL", "sqlite+aiosqlite:///./treg-test.db")
|
||||
# Under pytest-xdist each worker process gets its OWN file (gw0, gw1, …) — twelve workers against
|
||||
# one sqlite file drop each other's tables mid-test (1,022 errors on the first parallel run). An
|
||||
# explicit TREG_TEST_DB_URL wins untouched, for single-process runs against something specific.
|
||||
_worker = os.environ.get("PYTEST_XDIST_WORKER", "")
|
||||
_default = f"sqlite+aiosqlite:///./treg-test{'-' + _worker if _worker else ''}.db"
|
||||
os.environ["TREG_DATABASE_URL"] = os.environ.get("TREG_TEST_DB_URL", _default)
|
||||
os.environ["TREG_EMAIL_DEV_MODE"] = "true" # tests need the returned OTP code (prod default is now False)
|
||||
os.environ["TREG_RESEND_API_KEY"] = "" # never fire a real Resend send from the test suite (send_otp/send_invite skip when empty)
|
||||
os.environ["TREG_RUN_ALLOWED_BINS"] = "sh,echo,true,false,cat,sleep,treg-nonexistent-bin-xyz" # allow the test CLIs for --server run tests
|
||||
|
||||
Reference in New Issue
Block a user