Files
python-tdd/.woodpecker/main.yaml
Disco DeDisco f9cd08a510
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful
CI: FT stages run in parallel, --parallel dropped intra-stage — bud-btn click sites bypass scroll-into-view ; .woodpecker/main.yaml restructures the FT split — test-FTs-non-room + test-FTs-room now both depends_on: test-two-browser-FTs (instead of room serially depending on non-room) so they fan out + run concurrently, each w. its own DATABASE_URL: sqlite:////tmp/test_db_{non_room,room}.sqlite3 outside the shared workspace mount so the two stages can't see each other's SQLite file + the #296 EOFError-on-half-created-test-db blocker is gone; --parallel flag dropped from both manage.py test invocations because the empirical wall-clock from #302-304 (151 tests in ~42 min, 83 tests in ~16 min — ~16-17s/test avg either way) shows ~1-1.5x speedup at best — Firefox spawn cost (~3-5s cold-start × 38 tests/worker) + RAM pressure (4 headless Firefoxes ≈ 1.5-2GB working set on a quadcore DO droplet) + SQLite file-lock contention eat most of the gain the cores would otherwise give, while the contention amplifies every transient-DOM flake we've spent the last 2 days chasing (login-race in #300/303 → 054b0aa + ad0041d, gecko-perms in #302/303 → ad0041d, ElementNotInteractable in #304 → this commit, Jasmine-timeout in #303ad0041d); stage-level parallelism gets back the wall-clock reduction (~16min savings vs serial stages) w.o. amplifying within-stage contention — net wall clock should be ~60-65min for both FT stages running concurrently vs ~58min today, but flake exposure drops dramatically; downstream screendumps + build-and-push already list both FT steps in their depends_on so they naturally gate on the slower of the two ; test_core_bud_btn.py wraps 4 unwrapped find_element(...).click() sites in wait_for(execute_script("arguments[0].click()", btn)) — the _open_panel_and_invite helper feeding 6 GatekeeperBudBtnAsyncInviteTest tests + the standalone GatekeeperBudBtnDuplicateInviteErrorTest test_duplicate_invite_shows_error_brief_and_fyi_flashes_slot click + both .note-banner--duplicate .note-banner__fyi clicks (one on the share-flow at line 433, one on the gatekeeper-invite-flow at line 622) — pipeline #304 errored on the OK button click w. ElementNotInteractableException: Element <button id="id_bud_ok"> could not be scrolled into view because the post-send_keys autocomplete dropdown on _bud_invite_panel.html briefly overlapped the OK button under CI contention so Firefox refused the scroll-into-view; same pattern + same fix shape as confirm_guard in base.py — execute_script bypasses Selenium's scroll-into-view gate entirely + wait_for absorbs any leftover transient state via WebDriverException retry ; commit only ships the test-side fix + CI restructure; if the CI changes work as intended (green pipeline #305) the docker-rebuild option for python-tdd-ci:latest still stands as the next ~5min/pipeline win, separately filed in project_ci_remove_pip_install_deferred.md — TDD
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 14:12:38 -04:00

226 lines
8.1 KiB
YAML

services:
- name: postgres
image: postgres:16
environment:
POSTGRES_DB: python_tdd_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
- name: redis
image: redis:7
steps:
- name: test-UTs-n-ITs
image: gitea.earthmanrpg.me/discoman/python-tdd-ci:latest
environment:
DATABASE_URL: postgresql://postgres:postgres@postgres/python_tdd_test
CELERY_BROKER_URL: redis://redis:6379/0
REDIS_URL: redis://redis:6379/1
PIP_CACHE_DIR: .pip-cache
commands:
# `requirements.dev.txt` is the pinned superset Dockerfile.ci pre-
# installs; pinning here means pip skips resolver+download and just
# verifies "already satisfied" (~5-10s) instead of resolving unpinned
# requirements.txt against PyPI from scratch (~30-60s). Drift safety
# net: if requirements.dev.txt has changed since the CI image was
# last rebuilt + pushed, pip installs the delta — slower for that
# run but never broken. See TDD SKILL.md § CI dependency discipline.
- pip install -r requirements.dev.txt
- cd ./src
- python manage.py test apps
when:
- event: push
path:
- "src/**"
- "requirements.txt"
- ".woodpecker/main.yaml"
- name: test-two-browser-FTs
image: gitea.earthmanrpg.me/discoman/python-tdd-ci:latest
depends_on:
- test-UTs-n-ITs
environment:
HEADLESS: 1
CELERY_BROKER_URL: redis://redis:6379/0
REDIS_URL: redis://redis:6379/1
STRIPE_SECRET_KEY:
from_secret: stripe_secret_key
STRIPE_PUBLISHABLE_KEY:
from_secret: stripe_publishable_key
PIP_CACHE_DIR: .pip-cache
commands:
- pip install -r requirements.dev.txt
- cd ./src
# Also collectstatic'd here; output sits in the shared workspace so
# the downstream FT steps don't have to repeat it.
- python manage.py collectstatic --noinput
- python manage.py test functional_tests --tag=two-browser
- python manage.py test functional_tests --tag=sequential
- python manage.py test functional_tests --tag=channels
when:
- event: push
path:
- "src/**"
- "requirements.txt"
- ".woodpecker/main.yaml"
# ── FT split (stage-parallel, intra-stage sequential) ─────────────────
#
# test_game_room_* is the heaviest cluster — 9 Selenium-driven room-flow
# FTs that historically dominate the FT step wall-clock (~70% of the
# ~40-min single-step runs). Split off into its own step (`test-FTs-room`)
# so the partition is visible in the pipeline view; the non-room bucket
# is `test-FTs-non-room`. Both depend on test-two-browser-FTs only, so
# they fan out + run concurrently.
#
# The previous SQLite-collision blocker (pipeline #296: second step
# started against the first step's half-created `src/test_db.sqlite3`
# → Django interactive prompt → EOFError under non-interactive CI
# stdin) is resolved by giving each step a distinct `DATABASE_URL`
# pointing at its own sqlite file under /tmp — outside the shared
# workspace mount so the two stages can't see each other's DB.
#
# `--parallel` is dropped from both steps. Empirically (pipelines
# #302-304) it was giving ~1-1.5x speedup at most on these Selenium
# FTs — Firefox spawn cost + RAM pressure + SQLite file-lock contention
# eat most of the gain — while amplifying every transient-DOM flake
# (login-race, gecko-perms, ElementNotInteractable, Jasmine-timeout).
# Stage-level parallelism gives the same wall-clock reduction without
# contention amplification.
- name: test-FTs-non-room
image: gitea.earthmanrpg.me/discoman/python-tdd-ci:latest
depends_on:
- test-two-browser-FTs
environment:
HEADLESS: 1
# /tmp path (not workspace-relative) so the parallel test-FTs-room
# step can't see this DB + vice versa. See split-rationale above.
DATABASE_URL: sqlite:////tmp/test_db_non_room.sqlite3
CELERY_BROKER_URL: redis://redis:6379/0
REDIS_URL: redis://redis:6379/1
STRIPE_SECRET_KEY:
from_secret: stripe_secret_key
STRIPE_PUBLISHABLE_KEY:
from_secret: stripe_publishable_key
PIP_CACHE_DIR: .pip-cache
commands:
- pip install -r requirements.dev.txt
- cd ./src
# Every FT file EXCEPT test_game_room_* — that cluster runs in
# test-FTs-room. Channels + two-browser tags already covered upstream.
# `ls | grep -v | sed` enumerates module dotted-paths from filenames.
- python manage.py test --exclude-tag=channels --exclude-tag=two-browser $(ls functional_tests/test_*.py | grep -v 'test_game_room_' | sed 's|/|.|g;s|\.py||')
when:
- event: push
path:
- "src/**"
- "requirements.txt"
- ".woodpecker/main.yaml"
- name: test-FTs-room
image: gitea.earthmanrpg.me/discoman/python-tdd-ci:latest
depends_on:
- test-two-browser-FTs
environment:
HEADLESS: 1
# /tmp path (not workspace-relative) so test-FTs-non-room can't see
# this DB + vice versa. See split-rationale above.
DATABASE_URL: sqlite:////tmp/test_db_room.sqlite3
CELERY_BROKER_URL: redis://redis:6379/0
REDIS_URL: redis://redis:6379/1
STRIPE_SECRET_KEY:
from_secret: stripe_secret_key
STRIPE_PUBLISHABLE_KEY:
from_secret: stripe_publishable_key
PIP_CACHE_DIR: .pip-cache
commands:
- pip install -r requirements.dev.txt
- cd ./src
# Heavy Selenium room flows — 9 files (deck_contrib, gatekeeper,
# invite, select_role/sea/sig/sky, tray, tray_tooltip) isolated
# into their own sub-step. Runs in parallel w. test-FTs-non-room
# (distinct DATABASE_URL paths under /tmp; see split-rationale).
- python manage.py test --exclude-tag=channels --exclude-tag=two-browser $(ls functional_tests/test_game_room_*.py | sed 's|/|.|g;s|\.py||')
when:
- event: push
path:
- "src/**"
- "requirements.txt"
- ".woodpecker/main.yaml"
- name: screendumps
image: gitea.earthmanrpg.me/discoman/python-tdd-ci:latest
depends_on:
- test-FTs-non-room
- test-FTs-room
commands:
- cat ./src/functional_tests/screendumps/*.html || echo "No screendumps found"
when:
- event: push
status: failure
path:
- "src/**"
- "requirements.txt"
- ".woodpecker/main.yaml"
- name: build-and-push
image: docker:cli
depends_on:
- test-FTs-non-room
- test-FTs-room
environment:
REGISTRY_PASSWORD:
from_secret: gitea_registry_password
commands:
- echo "$REGISTRY_PASSWORD" | docker login gitea.earthmanrpg.me -u discoman --password-stdin
- docker build -t gitea.earthmanrpg.me/discoman/gamearray:latest .
- docker push gitea.earthmanrpg.me/discoman/gamearray:latest
when:
- branch: main
event: push
path:
- "src/**"
- "requirements.txt"
- "Dockerfile"
- ".woodpecker/main.yaml"
- name: deploy-staging
image: alpine
depends_on:
- build-and-push
environment:
SSH_KEY:
from_secret: deploy_ssh_key
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh -o StrictHostKeyChecking=no discoman@staging.earthmanrpg.me /opt/gamearray/deploy.sh
when:
- branch: main
event: push
path:
- "src/**"
- "requirements.txt"
- "Dockerfile"
- "infra/**"
- ".woodpecker/main.yaml"
- name: deploy-prod
image: alpine
depends_on:
- build-and-push
environment:
SSH_KEY:
from_secret: deploy_ssh_key
commands:
- apk add --no-cache openssh-client
- mkdir -p ~/.ssh
- printf '%s\n' "$SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- ssh -o StrictHostKeyChecking=no discoman@staging.earthmanrpg.me /opt/gamearray/deploy.sh
when:
- event: tag