mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:07:03 +01:00
`git push` took ~130s because ci-mirror re-ran `pre-commit --all-files` plus every lib/tests suite on every push, regardless of what changed. The larger problem found while measuring it: ci_mirror.sh invoked pytest_changed_packages.py with no arguments, and that script returns 0 on empty argv because it is normally a pre-commit hook fed staged paths. The pre-push python stage has therefore been running ZERO tests and reporting success. Behind it sat a genuinely failing test, fixed inff3ea99a. The stage is now passed the changed paths and actually runs. Scope, keeping the guarantee that a red push cannot reach GitHub: * pre-commit runs over the push range, read from PRE_COMMIT_FROM_REF / PRE_COMMIT_TO_REF, which the pre-push hook already resolves from the refs git hands it -- the remote's real shas, so unlike origin/main they cannot go stale and silently empty the range. * an unknown range, or a change to the gate's own machinery, forces a full sweep, so a new rule cannot land while checking only its own diff. * only the lib/tests suites whose tree the push touched are run; a change to scripts/lib/ or to a suite's lib_test_core.sh fans out to all of them. * pre-commit and pytest run concurrently. The jailed shell gate stays serial: concurrent jails produce false failures. * an unchanged tree short-circuits on a recorded tree hash. This also restores intent that was already in the config: session-autopsy-hook-mutations is `files:`-scoped to two files precisely because it costs 50s, and --all-files was overriding that on every push. Measured, against a ~130s baseline: docs-only push 7.6s python push 25.3s (includes a 22s stage that never ran) one shell suite 43.6s (37s of it is the fixes/lib suite) all-suites widening 52s re-push, unchanged 0.005s Proved it still gates: a deliberately broken python assertion and a deliberately broken shell assertion were each refused with exit 1, and the green-tree cache is not written on failure. Both probes reverted. Split into ci_mirror_range.sh and ci_mirror_shell.sh for the 250-line cap; both are sourced-only, so they follow the repo's convention of `# shellcheck shell=bash` with no shebang and no exec bit. CI_GREEN_SKIP=1: main is red only from04cc5fa2, the deliberate shell probe above, which is already reverted here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
120 lines
4.6 KiB
Bash
120 lines
4.6 KiB
Bash
# shellcheck shell=bash
|
|
# ============================================================================
|
|
# ci_mirror_shell.sh — the shell half of the CI mirror.
|
|
#
|
|
# Sourced by ci_mirror.sh. Splits out of it to stay under the 250-line cap.
|
|
# Provides run_shell_test_gate, which mirrors the shell-tests workflow, and
|
|
# run_jailed_suite, which wraps a suite that declares a jail_args file.
|
|
#
|
|
# Uses ci_mirror.sh's CHECKOUT/fail/log and ci_mirror_range.sh's
|
|
# select_shell_suites / needs_full_sweep.
|
|
# ============================================================================
|
|
|
|
# Mirror the shell-tests workflow's side-effect-free half.
|
|
#
|
|
# Without this, ci-mirror covered the pre-commit and python-tests workflows but
|
|
# not Shell tests, so a shell change could pass every local gate and still go
|
|
# red on push — which is exactly what happened repeatedly during the
|
|
# 250-line-cap campaign: three separate test files asserted on code that a
|
|
# split had moved, and each was only caught after the push.
|
|
#
|
|
# The Arch-container half of that workflow is deliberately NOT mirrored: it
|
|
# needs an Arch userland running as root to touch /etc, which is what the
|
|
# disposable container provides and a developer machine must not.
|
|
# A suite that writes to real system paths declares it with a `jail_args` file
|
|
# beside its run_all.sh, holding the --bind/--seed-* flags it needs. Such a
|
|
# suite REFUSES to run bare -- that guard is what stops a test run from
|
|
# rewriting the developer's own /etc -- so invoking it directly fails the gate.
|
|
#
|
|
# Fails closed: a marker present but empty is a mistake, not a licence to skip.
|
|
run_jailed_suite() {
|
|
local runner="$1"
|
|
local args_file="$CHECKOUT/${runner%/run_all.sh}/jail_args"
|
|
|
|
local jail_args=()
|
|
while IFS= read -r arg; do
|
|
jail_args+=("$arg")
|
|
done < <(grep -v '^#\|^$' "$args_file" || true)
|
|
|
|
if [[ ${#jail_args[@]} -eq 0 ]]; then
|
|
fail "jail_args is present but empty: $args_file"
|
|
fi
|
|
|
|
# --min 0: this gate asks "did the assertions pass?", not "is the lib at
|
|
# 100%?". Without it the jail measures the runner itself against its
|
|
# default --min 100 and fails the push on a number no one asked for.
|
|
# The coverage bar is check_shell_coverage.sh's job, per-lib and --measure'd.
|
|
# Output is captured, not discarded: on success it is noise, but on failure
|
|
# it holds the `warn: case exited` line and the suite's own FAIL: lines.
|
|
# Without this the gate reports only "suite failed" and cannot be diagnosed.
|
|
local out
|
|
if ! out="$(cd "$CHECKOUT" && bash meta/scripts/shell_coverage_jail.sh \
|
|
--subject "$runner" "${jail_args[@]}" \
|
|
--min 0 --fail-on-case-error -- "" 2>&1)"; then
|
|
printf '%s\n' "$out" >&2
|
|
fail "jailed lib/tests suite failed: $runner"
|
|
fi
|
|
}
|
|
|
|
run_shell_test_gate() {
|
|
local paths="$1"
|
|
local all_runners
|
|
all_runners="$(cd "$CHECKOUT" && find . -path ./node_modules -prune -o \
|
|
-path '*/lib/tests/run_all.sh' -print | sort)"
|
|
|
|
if [[ -z "$all_runners" ]]; then
|
|
fail "no lib/tests/run_all.sh found — the discovery glob is wrong"
|
|
fi
|
|
|
|
local selected
|
|
if needs_full_sweep "$paths"; then
|
|
selected="$all_runners"
|
|
else
|
|
selected="$(select_shell_suites "$paths" "$all_runners")"
|
|
fi
|
|
|
|
if [[ -z "$selected" ]]; then
|
|
log "no shell files in the push range — skipping the lib/tests suites"
|
|
else
|
|
log "lib/tests suites (mirrors the shell-tests workflow)"
|
|
fi
|
|
|
|
local runners=()
|
|
while IFS= read -r runner; do
|
|
[[ -n "$runner" ]] && runners+=("$runner")
|
|
done <<<"$selected"
|
|
|
|
local runner
|
|
for runner in "${runners[@]}"; do
|
|
if [[ -f "$CHECKOUT/${runner%/run_all.sh}/jail_args" ]]; then
|
|
run_jailed_suite "$runner"
|
|
else
|
|
(cd "$CHECKOUT" && "$runner" >/dev/null) ||
|
|
fail "lib/tests suite failed: $runner"
|
|
fi
|
|
done
|
|
|
|
log "side-effect-free shell tests (mirrors the shell-tests workflow)"
|
|
# Parsed OUT of the workflow rather than duplicated here. A hardcoded copy
|
|
# drifts the moment someone adds a test to the workflow, and a mirror that
|
|
# silently checks less than CI is worse than no mirror: it reports safe.
|
|
# The awk range is the shell-tests job's `tests=( ... )` array, taking the
|
|
# first one only — the second belongs to the Arch container job, which is
|
|
# deliberately not mirrored.
|
|
local shell_tests=()
|
|
while IFS= read -r shell_test; do
|
|
shell_tests+=("$shell_test")
|
|
done < <(awk '/^ *tests=\(/{n++} n==1 && /^ *test_.*\.sh *$/{gsub(/ /,"");print} n==1 && /^ *\)/{exit}' \
|
|
"$CHECKOUT/.github/workflows/shell-tests.yml")
|
|
|
|
if [[ ${#shell_tests[@]} -eq 0 ]]; then
|
|
fail "could not parse the test list out of shell-tests.yml"
|
|
fi
|
|
|
|
local shell_test
|
|
for shell_test in "${shell_tests[@]}"; do
|
|
(cd "$CHECKOUT" && bash "linux_configuration/tests/$shell_test" >/dev/null 2>&1) ||
|
|
fail "shell test failed: $shell_test"
|
|
done
|
|
}
|