diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0ca654ac..2d41dcb7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -9,9 +9,9 @@ name: Coverage # so it is not a status check on any commit and cannot gate merges. Coverage is # CPU-heavy, so a scheduled cadence keeps it off the critical path. # -# The coverage engine's own meta-tests (tests/unit/coverage_*_test.sh) are -# excluded from the measured run: executing them under --coverage double- -# instruments src/coverage.sh and corrupts their assertions. +# The coverage engine's own meta-tests (tests/unit/coverage/) are excluded from +# the measured run: executing them under --coverage double-instruments +# src/coverage/ and corrupts their assertions. on: schedule: @@ -51,7 +51,11 @@ jobs: # run well under the job timeout on the multi-core runner. Parallel LCOV # aggregation is deterministic (same total as a sequential run). run: | - files=$(ls tests/unit/*_test.sh | grep -v '/coverage_') + # `find`, not a glob: unit tests live in per-module subdirectories since + # #960, so a flat glob there matches nothing and `ls` fails the step. + # fixtures/ holds inputs to other tests, never tests themselves. + files=$(find tests/unit -name '*_test.sh' \ + -not -path '*/fixtures/*' -not -path '*/coverage/*') # shellcheck disable=SC2086 ./bashunit --coverage --parallel --jobs auto \ --coverage-report coverage/lcov.info --coverage-paths src $files diff --git a/tests/unit/project/ci_test_paths_test.sh b/tests/unit/project/ci_test_paths_test.sh index e605c730..e9967ad1 100644 --- a/tests/unit/project/ci_test_paths_test.sh +++ b/tests/unit/project/ci_test_paths_test.sh @@ -79,3 +79,52 @@ EOF assert_empty "$unresolved" } + +# The same rot hit a second workflow and this file did not catch it, because it +# only ever looked at tests.yml. coverage.yml globbed `tests/unit/*_test.sh` in a +# shell step rather than a `test_path:` key, so after #960 its `ls` failed and the +# nightly coverage run had been red for days -- unnoticed, because that workflow +# is deliberately non-blocking. +# +# This checks the property across every workflow and every shape: any tests/ +# path or glob a workflow names must resolve to something on disk. +function ci_referenced_test_paths() { + # Comment lines are dropped first: the workflows document the broken globs + # they replaced, and those must not be read as live references. + # + # The match is anchored on a boundary so `sample_tests/pass_test.sh` -- a file + # test-action.yml writes at runtime -- is not read as a `tests/` path. An + # unanchored `tests/` matched its tail and reported it missing. + "$GREP" -rhv '^[[:space:]]*#' "$ROOT_DIR"/.github/workflows/ --include='*.yml' 2>/dev/null | + "$GREP" -oE '(^|[[:space:]"'"'"'=])tests/[A-Za-z0-9_*/.-]+' | + sed 's/^[^t]//' | LC_ALL=C sort -u +} + +function test_every_tests_path_named_by_any_workflow_resolves() { + local unresolved="" + local token + while IFS= read -r token; do + [ -z "$token" ] && continue + case "$token" in + */) continue ;; + esac + + local found=0 + local match + for match in $token; do + if [ -e "$match" ]; then + found=1 + break + fi + done + + if [ "$found" -eq 0 ]; then + unresolved="$unresolved$token +" + fi + done <