parallelize full test gate

This commit is contained in:
Yisroel Baum 2026-08-15 22:34:31 +03:00
parent 0b0733cbec
commit ce5fa65f03
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9

159
justfile
View file

@ -1,30 +1,88 @@
set shell := ["bash", "-c"] set shell := ["bash", "-c"]
# How many jobs the parallel gate runs at once. Lower this when another
# worktree stack is active, or use 1 for a serial debugging run.
jobs := env('JUST_JOBS', '4')
default: default:
@just --list @just --list
# Full completion gate. Start the worktree stack before running it because # Full completion gate. Every check runs in one bounded pool, with likely
# Cypress exercises the frontend and its backend wiring. # long Cypress groups scheduled first and shorter jobs filling free lanes.
# Start the worktree stack before running it because Cypress needs Vite.
test-all: test-all:
@echo "==> frontend format + lint checks" just par "gate" {{ jobs }} \
just frontend-format-check frontend-cypress-account frontend-cypress-session \
just frontend-lint-check frontend-cypress-sets backend-types-check \
@echo "==> frontend type check" frontend-cypress-scheduling frontend-build \
just frontend-type-check backend-test-feature frontend-cypress-today \
@echo "==> backend static analysis" frontend-lint-check frontend-type-check backend-test-unit \
just backend-types-check frontend-format-check cypress-spec-coverage
@echo "==> frontend production build"
just frontend-build # Run recipes concurrently, buffering each one's output to its own log.
@echo "==> backend tests" # Report jobs as they finish, then print every failed job's complete log.
just backend-test [private]
@echo "==> frontend Cypress tests" par label max +targets:
just frontend-cypress-run #!/usr/bin/env bash
set -uo pipefail
targets=({{ targets }})
logs=$(mktemp -d)
declare -A target_of started_at
failed=()
next=0
running=0
echo "==> {{ label }} (${#targets[@]} jobs, up to {{ max }} at a time)"
while (( next < ${#targets[@]} || running > 0 )); do
while (( next < ${#targets[@]} && running < {{ max }} )); do
target="${targets[next]}"
just "$target" > "$logs/$target.log" 2>&1 &
target_of[$!]="$target"
started_at[$!]=$SECONDS
next=$(( next + 1 ))
running=$(( running + 1 ))
done
wait -n -p finished
status=$?
running=$(( running - 1 ))
target="${target_of[$finished]}"
elapsed=$(( SECONDS - started_at[$finished] ))
if (( status == 0 )); then
printf ' ok %-28s %4ds\n' "$target" "$elapsed"
else
printf ' FAIL %-28s %4ds\n' "$target" "$elapsed"
failed+=("$target")
fi
done
if (( ${#failed[@]} == 0 )); then
rm -rf "$logs"
exit 0
fi
for target in "${failed[@]}"; do
printf '\n--- %s ---\n' "$target"
cat "$logs/$target.log"
done
printf '\n{{ label }} failed: %s\n' "${failed[*]}"
printf 'logs kept in %s\n' "$logs"
exit 1
# Backend # Backend
backend-test *args: backend-test *args:
cd backend && php artisan test {{ args }} cd backend && php artisan test {{ args }}
backend-test-unit *args:
cd backend && php artisan test --testsuite=Unit {{ args }}
backend-test-feature *args:
cd backend && php artisan test --testsuite=Feature {{ args }}
backend-types-check: backend-types-check:
cd backend && composer types:check cd backend && composer types:check
@ -53,3 +111,74 @@ frontend-build:
frontend-cypress-run: frontend-cypress-run:
cd frontend/website && npm run test:e2e cd frontend/website && npm run test:e2e
# Each Cypress spec must belong to exactly one feature group. The complete
# gate runs these groups concurrently; the whole-suite recipe above remains
# available for focused iteration.
frontend_specs_account := "confirm-email login signup"
frontend_specs_session := "guest-auth session-auth"
frontend_specs_sets := "set-layout sets-dashboard"
frontend_specs_scheduling := "set-scheduling"
frontend_specs_today := "today-assignments"
frontend-cypress-account:
just _cypress {{ frontend_specs_account }}
frontend-cypress-session:
just _cypress {{ frontend_specs_session }}
frontend-cypress-sets:
just _cypress {{ frontend_specs_sets }}
frontend-cypress-scheduling:
just _cypress {{ frontend_specs_scheduling }}
frontend-cypress-today:
just _cypress {{ frontend_specs_today }}
[private]
_cypress +names:
cd frontend/website && specs=$(for name in {{ names }}; do \
printf 'cypress/e2e/%s.cy.ts,' "$name"; done) && \
npm run test:e2e -- --spec "${specs%,}"
cypress-spec-coverage:
just _spec-coverage {{ frontend_specs_account }} \
{{ frontend_specs_session }} {{ frontend_specs_sets }} \
{{ frontend_specs_scheduling }} {{ frontend_specs_today }}
[private]
_spec-coverage +names:
#!/usr/bin/env bash
set -uo pipefail
cd frontend/website
listed=$(printf '%s\n' {{ names }} | sort)
on_disk=$(ls cypress/e2e/*.cy.ts | xargs -n1 basename \
| sed 's/\.cy\.ts$//' | sort)
duplicated=$(echo "$listed" | uniq -d)
unique=$(echo "$listed" | uniq)
ungrouped=$(comm -13 <(echo "$unique") <(echo "$on_disk"))
missing=$(comm -23 <(echo "$unique") <(echo "$on_disk"))
status=0
if [ -n "$ungrouped" ]; then
echo "in no group, so never runs in the gate:"
printf ' %s\n' $ungrouped
status=1
fi
if [ -n "$missing" ]; then
echo "listed in a group but not on disk:"
printf ' %s\n' $missing
status=1
fi
if [ -n "$duplicated" ]; then
echo "in more than one group, so runs twice:"
printf ' %s\n' $duplicated
status=1
fi
exit $status