Merge branch 'parallel-test-gate'
This commit is contained in:
commit
0bd2b357cf
4 changed files with 159 additions and 19 deletions
|
|
@ -131,6 +131,9 @@ intentionally unclaimed; the built-in health endpoint is `/up`.
|
|||
- Run the focused PHPUnit test while developing.
|
||||
- Use `just backend-types-check` for Larastan and `just backend-test` for the
|
||||
full PHPUnit suite during iteration.
|
||||
- The completion gate runs `backend-test-unit` and `backend-test-feature` as
|
||||
concurrent processes. This is safe because each process owns its own
|
||||
in-memory SQLite database.
|
||||
- Fix failures caused by the change. Report unrelated baseline failures
|
||||
precisely rather than expanding scope silently.
|
||||
- The shared `just test-all` command is the required completion gate. Focused
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ backend and does not proxy the frontend.
|
|||
|
||||
- Cypress is configured under `cypress/` and runs through `npm run test:e2e`
|
||||
or `just frontend-cypress-run`.
|
||||
- Every Cypress spec must appear in exactly one `frontend_specs_*` group in
|
||||
the root `justfile`. The completion gate runs those groups concurrently,
|
||||
and its `cypress-spec-coverage` job rejects ungrouped, duplicate, or missing
|
||||
specs. Keep `frontend-cypress-run` for whole-suite iteration.
|
||||
- Prefer the cheapest test seam that proves the behavior. Cypress covers
|
||||
routing, browser forms, authentication flows, request wiring, and responsive
|
||||
behavior.
|
||||
|
|
|
|||
10
ai/shared.md
10
ai/shared.md
|
|
@ -223,12 +223,16 @@ gate passes against that worktree:
|
|||
3. Run the complete gate from the worktree root:
|
||||
|
||||
```sh
|
||||
direnv exec "$(git rev-parse --show-toplevel)" just test-all
|
||||
JUST_JOBS=4 direnv exec "$(git rev-parse --show-toplevel)" just test-all
|
||||
```
|
||||
|
||||
4. Do not hand-assemble a substitute from focused commands. `test-all` runs
|
||||
frontend format and lint checks, frontend type checking, Larastan, the
|
||||
production build, PHPUnit, and Cypress in fail-fast order.
|
||||
one pool of 13 jobs: frontend format, lint, type, build, and Cypress
|
||||
groups; Larastan; the PHPUnit Unit and Feature suites; and the Cypress
|
||||
spec coverage guard. Jobs report as they finish, failed output is buffered
|
||||
and printed at the end, and every job runs even after another job fails.
|
||||
`JUST_JOBS` limits concurrency and defaults to 4. Use 2 when resources are
|
||||
constrained or 1 for a serial debugging run.
|
||||
5. Everything must pass before completion. Report exact baseline or
|
||||
environmental failures rather than hiding them.
|
||||
6. If the stack was started only for validation, stop it when finished:
|
||||
|
|
|
|||
161
justfile
161
justfile
|
|
@ -1,29 +1,87 @@
|
|||
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:
|
||||
@just --list
|
||||
|
||||
# Full completion gate. Start the worktree stack before running it because
|
||||
# Cypress exercises the frontend and its backend wiring.
|
||||
# Full completion gate. Every check runs in one bounded pool, with likely
|
||||
# long Cypress groups scheduled first and shorter jobs filling free lanes.
|
||||
# Start the worktree stack before running it because Cypress needs Vite.
|
||||
test-all:
|
||||
@echo "==> frontend format + lint checks"
|
||||
just frontend-format-check
|
||||
just frontend-lint-check
|
||||
@echo "==> frontend type check"
|
||||
just frontend-type-check
|
||||
@echo "==> backend static analysis"
|
||||
just backend-types-check
|
||||
@echo "==> frontend production build"
|
||||
just frontend-build
|
||||
@echo "==> backend tests"
|
||||
just backend-test
|
||||
@echo "==> frontend Cypress tests"
|
||||
just frontend-cypress-run
|
||||
just par "gate" {{ jobs }} \
|
||||
frontend-cypress-account frontend-cypress-session \
|
||||
frontend-cypress-sets backend-types-check \
|
||||
frontend-cypress-scheduling frontend-build \
|
||||
backend-test-feature frontend-cypress-today \
|
||||
frontend-lint-check frontend-type-check backend-test-unit \
|
||||
frontend-format-check cypress-spec-coverage
|
||||
|
||||
# Run recipes concurrently, buffering each one's output to its own log.
|
||||
# Report jobs as they finish, then print every failed job's complete log.
|
||||
[private]
|
||||
par label max +targets:
|
||||
#!/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-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:
|
||||
cd backend && composer types:check
|
||||
|
|
@ -53,3 +111,74 @@ frontend-build:
|
|||
|
||||
frontend-cypress-run:
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue