cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

main
goq / scripts / tests / demo-gate-stages.sh
demo-gate-stages.sh 8.8K executable
1#!/usr/bin/env bash2set -euo pipefail34# Prove that splitting the complete demo gate into stages did not drop a check.5#6# The gate is now run by CI as parallel legs, one stage each, so the dangerous7# regression is a check that belongs to no stage and therefore silently stops8# running. This exercises the real gate script against stubbed tools in a9# throwaway repository and asserts two things:10#11# 1. every check in a required inventory actually executes, and12# 2. running the stages one at a time spends exactly the same commands as the13# default complete run.14#15# Static structure is checked separately in verify_ci_cross_build_policy.py.16# This is the dynamic half, and it is what catches an early `exit 0` or a17# conditional wrapped around a stage body.1819script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"20repo_dir="$(CDPATH='' cd -- "$script_dir/../.." && pwd -P)"21temp_parent="${TMPDIR:-/tmp}"22temp_parent="${temp_parent%/}"23temp_root="$(mktemp -d "$temp_parent/goq-demo-gate-stages.XXXXXX")"2425cleanup() {26 local status=$?27 trap - EXIT INT TERM HUP28 case "$temp_root" in29 "$temp_parent"/goq-demo-gate-stages.??????) rm -rf -- "$temp_root" ;;30 esac31 exit "$status"32}33trap cleanup EXIT INT TERM HUP3435fake_repo="$temp_root/repo"36stub_bin="$temp_root/bin"37fake_home="$temp_root/home"38mkdir -p "$fake_repo/scripts" "$fake_repo/portal" "$stub_bin" "$fake_home"3940# Stub logs live outside the fake repository so they cannot make41# `git diff --check` see a dirty worktree.42#43# The generated stub bodies below deliberately keep `$*` and `$GOQ_STAGE_LOG`44# unexpanded: they must expand when the stub runs, not when it is written.45write_stub() {46 local name="$1"47 shift48 {49 printf '%s\n' '#!/usr/bin/env bash' 'set -euo pipefail'50 # shellcheck disable=SC201651 printf 'printf "%%s\\n" "%s $*" >>"$GOQ_STAGE_LOG"\n' "$name"52 printf '%s\n' "$@"53 } >"$stub_bin/$name"54 chmod 0755 "$stub_bin/$name"55}5657# `cargo test ... -- --list` output has to satisfy require_rust_test, and58# `cargo tree` output has to satisfy the dependency boundary greps.59# shellcheck disable=SC201660write_stub cargo '61for arg in "$@"; do62 if [[ "$arg" == "--list" ]]; then63 printf "%s\n" \64 "stub::resolves_pinned_upstream_gamescope_pipewire_contract: test" \65 "stub::encoder_control_coalesces_latest_state_and_acknowledges_only_configured_idr: test" \66 "stub::in_process_gstreamer_x264_smoke: test"67 exit 068 fi69done70if [[ "${1:-}" == "tree" ]]; then71 printf "%s\n" "stub-crate v0.0.0"72 exit 073fi74if [[ "${1:-}" == "--version" ]]; then75 printf "%s\n" "cargo 1.95.0 (stub)"76fi77exit 0'78write_stub rustc 'printf "%s\n" "rustc 1.95.0 (stub)"'79write_stub node 'exit 0'80write_stub ffmpeg 'exit 0'81write_stub shellcheck 'exit 0'82write_stub gst-inspect-1.0 'exit 0'83write_stub cargo-zigbuild 'printf "%s\n" "cargo-zigbuild 0.23.0"'84write_stub zig 'printf "%s\n" "0.16.0"'8586# Helper scripts the gate shells out to become logging stubs too, so their87# arguments are observable without running a real loopback session.88write_helper() {89 local relative="$1"90 local name="$2"91 {92 printf '%s\n' '#!/usr/bin/env bash' 'set -euo pipefail'93 # shellcheck disable=SC201694 printf 'printf "%%s\\n" "%s $*" >>"$GOQ_STAGE_LOG"\n' "$name"95 printf '%s\n' 'exit 0'96 } >"$fake_repo/$relative"97 chmod 0755 "$fake_repo/$relative"98}99100write_helper scripts/run-linux-cross-build-gate.sh cross-build-gate101write_helper scripts/loopback-proof.sh loopback-proof102write_helper scripts/run-shell-tests.sh run-shell-tests103104cp "$repo_dir/scripts/verify-demo-build.sh" "$fake_repo/scripts/verify-demo-build.sh"105chmod 0755 "$fake_repo/scripts/verify-demo-build.sh"106107printf '%s\n' 'export const stub = 1;' >"$fake_repo/portal/main.js"108printf '%s\n' 'export const stub = 2;' >"$fake_repo/portal/diagnostics.mjs"109printf '%s\n' 'export const stub = 3;' >"$fake_repo/portal/stub.test.mjs"110111GIT_AUTHOR_NAME=stub GIT_AUTHOR_EMAIL=stub@example.invalid \112 GIT_COMMITTER_NAME=stub GIT_COMMITTER_EMAIL=stub@example.invalid \113 git -C "$fake_repo" -c init.defaultBranch=main init --quiet114git -C "$fake_repo" add -A115GIT_AUTHOR_NAME=stub GIT_AUTHOR_EMAIL=stub@example.invalid \116 GIT_COMMITTER_NAME=stub GIT_COMMITTER_EMAIL=stub@example.invalid \117 git -C "$fake_repo" commit --quiet -m 'stub repository'118119run_gate() {120 local log="$1"121 shift122 : >"$log"123 # HOME points at an empty directory so the gate cannot source a real124 # ~/.cargo/env and put the genuine toolchain ahead of the stubs on PATH.125 env -i \126 HOME="$fake_home" \127 PATH="$stub_bin:/usr/bin:/bin:/usr/sbin:/sbin" \128 GOQ_STAGE_LOG="$log" \129 GOQ_VERIFY_IN_PROCESS_GSTREAMER=1 \130 GOQ_REQUIRE_LINUX_CROSS_BUILD=1 \131 LC_ALL=C \132 bash "$fake_repo/scripts/verify-demo-build.sh" "$@" \133 >"$log.stdout" 2>"$log.stderr" || {134 printf 'gate invocation failed: %s\n' "$*" >&2135 sed -n '1,60p' "$log.stderr" >&2136 exit 1137 }138}139140complete_log="$temp_root/complete.log"141run_gate "$complete_log"142143if ! grep -Fxq 'demo_build_preflight=ok' "$complete_log.stdout"; then144 printf 'the complete gate did not report success\n' >&2145 exit 1146fi147148# Every check the single serial job used to run. A stage that stops reaching one149# of these fails here rather than silently passing in CI.150required_commands=(151 'cargo fmt --all -- --check'152 'cargo test --locked --workspace --all-targets'153 'cargo clippy --locked --workspace --all-targets -- -D warnings'154 'cargo check --locked -p portal --all-targets --features experimental-non-macos-pointer-capture'155 'cargo build --locked -p sigil-host --bin sigil'156 'cargo tree --locked -p sigil-host --edges normal'157 'cargo tree --locked -p portal --edges normal'158 'cargo check --locked -p sigil-host --all-targets --features in-process-gstreamer'159 'cargo test --locked -p sigil-host --features in-process-gstreamer in_process_gstreamer_x264_smoke -- --ignored --nocapture'160 'cargo test --locked -p sigil-host encoder_control_coalesces_latest_state_and_acknowledges_only_configured_idr'161 'cargo test --locked -p portal --release commands::state::tests::rejects_direct_node_when_debug_mode_is_disabled -- --exact'162 'cargo test --locked -p portal --release commands::state::tests::ordinary_release_excludes_direct_node_bypass -- --exact'163 'cargo test --locked -p portal --release --features demo-direct-node commands::state::tests::app_state_accepts_direct_node_only_in_debug_builds -- --exact'164 'cargo test --locked -p sigil-host --release tests::ordinary_release_excludes_configured_host_auth_bypass -- --exact'165 'cargo test --locked -p sigil-host --release --features demo-auth-bypass tests::configured_host_auth_bypass_is_explicitly_build_contained -- --exact'166 'cross-build-gate '167 'run-shell-tests scripts/tests'168 'shellcheck '169 'node --check portal/main.js'170 'node --check portal/diagnostics.mjs'171 'node --test portal/stub.test.mjs'172 'gst-inspect-1.0 videotestsrc'173 'gst-inspect-1.0 x264enc'174 'gst-inspect-1.0 appsink'175 'loopback-proof '176 'loopback-proof --profile debug --control-v2 --viewers 3 --primary-frames 600 --reconnect-cycles 3'177 'loopback-proof --profile debug --control-v2 --viewers 3 --focus-handoffs 3 --assert-neutral-before-successor --primary-frames 600 --reconnect-cycles 3'178 'loopback-proof --profile release --legacy-exclusive --viewers 2 --expect-second-rejected'179)180181missing=0182for required in "${required_commands[@]}"; do183 if ! grep -Fq -- "$required" "$complete_log"; then184 printf 'the complete gate never ran: %s\n' "$required" >&2185 missing=1186 fi187done188if (( missing != 0 )); then189 printf 'complete gate command log follows\n' >&2190 sed -n '1,200p' "$complete_log" >&2191 exit 1192fi193194# Running the stages one at a time must spend exactly the same commands as the195# default complete run. This is what makes the parallel CI legs equivalent to196# the serial job they replaced.197union_log="$temp_root/union.log"198: >"$union_log"199for stage in quick cross native gstreamer repo-tests containment; do200 stage_log="$temp_root/stage-$stage.log"201 run_gate "$stage_log" --stage "$stage"202 cat "$stage_log" >>"$union_log"203done204for case_number in 1 2 3 4; do205 stage_log="$temp_root/stage-loopback-$case_number.log"206 run_gate "$stage_log" --stage loopback --loopback-case "$case_number"207 cat "$stage_log" >>"$union_log"208done209210# Each invocation prints its own toolchain banner, so ten stage runs report it211# ten times. That is diagnostics rather than a check, so compare the checks.212strip_banner() {213 grep -Fvx -e 'cargo --version' -e 'rustc --version' "$1" | sort214}215216strip_banner "$complete_log" >"$temp_root/complete.sorted"217strip_banner "$union_log" >"$temp_root/union.sorted"218if ! diff -u "$temp_root/complete.sorted" "$temp_root/union.sorted" \219 >"$temp_root/union.diff"; then220 printf 'the per-stage runs do not spend the same commands as the complete gate\n' >&2221 printf '(-) only in the complete run, (+) only in the per-stage runs\n' >&2222 sed -n '1,80p' "$temp_root/union.diff" >&2223 exit 1224fi225226printf 'demo_gate_stage_coverage=ok\n'