#!/usr/bin/env bash # Usage: bash .woodpecker/_retry_failed.sh # # Runs `python manage.py test "$@"`. If any tests fail/error, parses the # failure labels out of stdout and re-runs ONLY those tests — so a single # Selenium flake at test 90/93 costs ~22s on retry instead of the full # 35-minute step. # # Django's unittest-based runner prints failures in a predictable shape: # # ERROR: test_method (full.dotted.path.TestClass.test_method) # FAIL: test_method (full.dotted.path.TestClass.test_method) # # The dotted path inside the parens is exactly what `manage.py test` # accepts as a label. We grep for those lines + re-run that list. # # Exit semantics: # - First run green → exit 0, no retry. # - First run failed AND label parse found nothing (crashed before any # test reported, e.g. ImportError) → propagate first-run exit code, # no retry. Genuine infra problems shouldn't be silently re-run. # - First run failed AND labels parsed → retry just those; exit with # the retry's exit code. A real (not-flaky) regression fails twice # → step still red, with the focused retry log as the authoritative # report (no need to scroll past the noisy first-run output). # # Run from inside `src/` (Woodpecker preserves cwd across `commands:`, # so the upstream `cd ./src` carries through). set +e # do NOT bail on first failure; we WANT to handle it LOG=$(mktemp -t ft-retry.XXXXXX.log) trap 'rm -f "$LOG"' EXIT echo "──── First run ────" python manage.py test "$@" 2>&1 | tee "$LOG" FIRST=${PIPESTATUS[0]} if [ "$FIRST" -eq 0 ]; then exit 0 fi # Parse failure labels. Match both FAIL: and ERROR: lines; the dotted # path lives inside the trailing parens. `sort -u` dedupes if a single # test produces multiple lines (rare but possible). FAILED=$(grep -E '^(FAIL|ERROR): ' "$LOG" \ | sed -E 's/^.*\(([^)]+)\)[^()]*$/\1/' \ | sort -u \ | tr '\n' ' ') if [ -z "$FAILED" ]; then echo "──── First run failed, but no FAIL/ERROR labels parseable ────" echo "──── Not retrying — likely an infra problem, not a test flake ────" exit "$FIRST" fi NUM=$(echo "$FAILED" | wc -w | tr -d ' ') echo "" echo "──── Retry ($NUM failed test(s) from first run) ────" echo "$FAILED" | tr ' ' '\n' | sed 's/^/ /' echo "─────────────────────────────────────────────────────" echo "" python manage.py test $FAILED