#!/usr/bin/env bash # check_the_checker for scripts/checkers.sh. Every case injects a fault that # a bare `./scripts/X-test.sh` cannot see, and asserts the runner does. # See context/test-evidence/tooling.md. set -uo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUNNER="$here/checkers.sh" work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT pass=0; fail=0 ok() { printf ' ok %s\n' "$1"; pass=$((pass+1)); } bad() { printf ' FAIL %s (%s)\n' "$1" "$2"; fail=$((fail+1)); } # A sandbox is a copy of the runner beside fixture suites, so `$HERE` inside it # is the sandbox: the shebang scan and the suite list are both the fixtures'. sandbox() { local d="$work/$1" mkdir -p "$d" cp "$RUNNER" "$d/checkers.sh" chmod +x "$d/checkers.sh" printf '%s' "$d" } # A suite is only discovered when the thing it checks is present, so every # fixture gets a stub subject beside it. suite() { local d="$1" name="$2" cat > "$d/$name-test.sh" chmod +x "$d/$name-test.sh" printf '#!/usr/bin/env bash\nexit 0\n' > "$d/$name.sh" chmod +x "$d/$name.sh" } # $1 name, $2 expected exit, rest: the command. Nothing follows the runner in # its own pipeline, which is the rule its own header states. expect() { local what="$1" want="$2"; shift 2 "$@" > "$work/out" 2>&1 local got=$? [ "$got" = "$want" ] && ok "$what" || bad "$what" "exit $got, wanted $want" } # --------------------------------------------------------------------------- # 1. The fault this runner exists for. `nix develop` exports `name`; a suite # that reads an unset variable under `set -u` is therefore SILENT in the dev # shell and fatal in CI. That is improvise-f9qf, reproduced exactly. d="$(sandbox setu)" suite "$d" masked <<'EOF' #!/usr/bin/env bash set -euo pipefail echo "read: $name" EOF export name=nix-shell-env expect "a suite reading an inherited dev-shell variable fails under the runner" \ 1 "$d/checkers.sh" masked # The same suite run the way the old gate ran it PASSES, which is what makes # the case above discriminating rather than a test of `set -u`. "$d/masked-test.sh" > "$work/out" 2>&1 direct=$? [ "$direct" = 0 ] \ && ok "and passes when run directly with that variable exported" \ || bad "and passes when run directly with that variable exported" \ "exit $direct, wanted 0" unset name # --------------------------------------------------------------------------- # 2. The shebang rule. `#!/bin/sh` is dash in CI and bash-in-sh-mode on macOS. d="$(sandbox shebang)" suite "$d" fine <<'EOF' #!/usr/bin/env bash exit 0 EOF printf '#!/bin/sh\nexit 0\n' > "$d/legacy.sh" chmod +x "$d/legacy.sh" expect "a #!/bin/sh script in the scripts directory is refused" 1 \ "$d/checkers.sh" fine grep -q 'wanted #!/usr/bin/env bash' "$work/out" \ && ok "and the refusal names the shebang it wanted" \ || bad "and the refusal names the shebang it wanted" "no such message" # Without the injected file the same sandbox passes, so the case above is not # passing on some other complaint. rm -f "$d/legacy.sh" expect "and the same sandbox passes once it is removed" 0 "$d/checkers.sh" fine # --------------------------------------------------------------------------- # 3. A failing suite is reported, and a passing one is not. d="$(sandbox rc)" suite "$d" red <<'EOF' #!/usr/bin/env bash exit 1 EOF expect "a failing suite fails the runner" 1 "$d/checkers.sh" red d="$(sandbox green)" suite "$d" blue <<'EOF' #!/usr/bin/env bash exit 0 EOF expect "a passing suite passes the runner" 0 "$d/checkers.sh" blue # --------------------------------------------------------------------------- # 4. A named suite that does not exist is a usage error, not a silent no-op. d="$(sandbox missing)" suite "$d" present <<'EOF' #!/usr/bin/env bash exit 0 EOF expect "an unknown suite name is exit 4" 4 "$d/checkers.sh" absent # --------------------------------------------------------------------------- # 5. --list names every suite in the directory and nothing else. d="$(sandbox listing)" suite "$d" alpha <<'EOF' #!/usr/bin/env bash exit 0 EOF suite "$d" beta <<'EOF' #!/usr/bin/env bash exit 0 EOF "$d/checkers.sh" --list > "$work/out" 2>&1 listed="$(tr '\n' ' ' < "$work/out")" [ "$listed" = "alpha beta " ] && ok "--list names exactly the suites present" \ || bad "--list names exactly the suites present" "got [$listed]" # A test RUNNER also ends in -test.sh. It is not a suite, and running it as # one is how `scripts/node-test.sh` printed its usage inside a green gate. printf '#!/usr/bin/env bash\nexit 4\n' > "$d/gamma-test.sh" chmod +x "$d/gamma-test.sh" "$d/checkers.sh" --list > "$work/out" 2>&1 listed="$(tr '\n' ' ' < "$work/out")" [ "$listed" = "alpha beta " ] \ && ok "an X-test.sh with no X beside it is not a suite" \ || bad "an X-test.sh with no X beside it is not a suite" "got [$listed]" # --------------------------------------------------------------------------- # 8. --would-run reports what the rest of the arguments run, and runs nothing. # check-ci-graph.py asks it, so each argument rule below is answered HERE. d="$(sandbox wouldrun)" for n in alpha beta; do suite "$d" "$n" < "$work/out" 2>&1 got="$(tr '\n' ' ' < "$work/out")" [ "$got" = "would-run: alpha would-run: beta " ] && [ ! -e "$work/ran-alpha" ] \ && ok "--would-run with no names reports every suite and runs none" \ || bad "--would-run with no names reports every suite and runs none" "got [$got]" "$d/checkers.sh" --would-run --self-test alpha > "$work/out" 2>&1 got="$(tr '\n' ' ' < "$work/out")" [ "$got" = "missing: checkers " ] \ && ok "--would-run --self-test reports checkers and discards the names" \ || bad "--would-run --self-test reports checkers and discards the names" "got [$got]" "$d/checkers.sh" --would-run --list alpha > "$work/out" 2>&1 grep -q '^would-run:' "$work/out" \ && bad "--would-run --list reports nothing as run" "$(tr '\n' ' ' < "$work/out")" \ || ok "--would-run --list reports nothing as run" expect "--would-run anywhere but first is a usage error" 4 "$d/checkers.sh" alpha --would-run [ ! -e "$work/ran-alpha" ] && ok "and it runs nothing" \ || bad "and it runs nothing" "alpha ran" # --------------------------------------------------------------------------- # 9. --ci runs that job's row of the CI table and nothing else. The # sandbox has none of the real suites, so a row reads back as `missing:`. "$d/checkers.sh" --would-run --ci js > "$work/out" 2>&1 got="$(tr '\n' ' ' < "$work/out")" [ "$got" = "missing: node-test " ] && ok "--ci reads that job's row of the table" \ || bad "--ci reads that job's row of the table" "got [$got]" expect "--ci with a job the table does not have is exit 4" 4 "$d/checkers.sh" --would-run --ci nope expect "--ci with nothing after it is exit 4" 4 "$d/checkers.sh" --would-run --ci expect "--ci beside a suite name is exit 4, not a union" 4 \ "$d/checkers.sh" --would-run --ci js alpha expect "--ci beside --self-test is exit 4" 4 "$d/checkers.sh" --would-run --self-test --ci js printf '\ncheckers-test: %d passed, %d failed\n' "$pass" "$fail" [ "$fail" -eq 0 ]