Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 35 additions & 11 deletions .githooks/validate-codeql.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,46 @@
CODEQL_FILE="$SCAN_PATH/.github/workflows/codeql.yml"
[ -f "$CODEQL_FILE" ] || exit 0

# Detect languages
HAS_JS=$(find "$SCAN_PATH" -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" 2>/dev/null | head -1)
HAS_PY=$(find "$SCAN_PATH" -name "*.py" 2>/dev/null | head -1)
HAS_GO=$(find "$SCAN_PATH" -name "*.go" 2>/dev/null | head -1)
HAS_RS=$(find "$SCAN_PATH" -name "*.rs" 2>/dev/null | head -1)
# Detect languages.
#
# `find ... | head -1` is NOT safe here. This script runs under
# `set -euo pipefail`; when `head` exits after the first match, `find` is killed
# by SIGPIPE, the pipeline reports 141, and `set -e` terminates this script with
# NO OUTPUT. The hook passes an ABSOLUTE INPUT_PATH, under which a match is
# found early with tree left to walk, so the gate failed 6 times out of 6 —
# silently, on a perfectly valid repository. `-print -quit` stops find itself
# after the first hit and needs no pipe.
#
# The -name alternations are also parenthesised: without the group, `-o` binds
# loosely and the implicit -print does not apply as intended.
first_match() {
find "$SCAN_PATH" \( "$@" \) -print -quit 2>/dev/null
}
HAS_JS=$(first_match -name '*.js' -o -name '*.ts' -o -name '*.jsx' -o -name '*.tsx')
HAS_PY=$(first_match -name '*.py')
HAS_GO=$(first_match -name '*.go')
HAS_RS=$(first_match -name '*.rs')

# Check for unsupported languages
[[ "$HAS_PY" ]] && ! grep -q "language:.*'python'" "$CODEQL_FILE" && echo "[validate-codeql] WARNING: Python files but no Python in CodeQL" >&2
[[ "$HAS_GO" ]] && ! grep -q "language:.*'go'" "$CODEQL_FILE" && echo "[validate-codeql] WARNING: Go files but no Go in CodeQL" >&2
[[ "$HAS_JS" ]] && ! grep -q "language:.*'javascript'" "$CODEQL_FILE" && echo "[validate-codeql] WARNING: JS files but no JavaScript in CodeQL" >&2
# Check for unsupported languages.
#
# These must be `if`, not `[[ ... ]] && ... && echo`. Under `set -e` an AND-OR
# list whose guard is false returns 1, which terminates the script — so the
# "no Python present" case would abort the gate instead of passing it.
if [ -n "$HAS_PY" ] && ! grep -q "language:.*'python'" "$CODEQL_FILE"; then

Check failure on line 35 in .githooks/validate-codeql.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag196SEuIT-Jpkl&open=AaCg8Ag196SEuIT-Jpkl&pullRequest=780
echo "[validate-codeql] WARNING: Python files but no Python in CodeQL" >&2
fi
if [ -n "$HAS_GO" ] && ! grep -q "language:.*'go'" "$CODEQL_FILE"; then

Check failure on line 38 in .githooks/validate-codeql.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag196SEuIT-Jpkm&open=AaCg8Ag196SEuIT-Jpkm&pullRequest=780
echo "[validate-codeql] WARNING: Go files but no Go in CodeQL" >&2
fi
if [ -n "$HAS_JS" ] && ! grep -q "language:.*'javascript'" "$CODEQL_FILE"; then

Check failure on line 41 in .githooks/validate-codeql.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag196SEuIT-Jpkn&open=AaCg8Ag196SEuIT-Jpkn&pullRequest=780
echo "[validate-codeql] WARNING: JS files but no JavaScript in CodeQL" >&2
fi

# Rust/OCaml not supported
[[ "$HAS_RS" ]] && grep -q "language:.*'rust'" "$CODEQL_FILE" && {
if [ -n "$HAS_RS" ] && grep -q "language:.*'rust'" "$CODEQL_FILE"; then

Check failure on line 46 in .githooks/validate-codeql.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag196SEuIT-Jpko&open=AaCg8Ag196SEuIT-Jpko&pullRequest=780
echo "[validate-codeql] ERROR: CodeQL does not support Rust - use ['actions']" >&2
exit 1
}
fi

echo "[validate-codeql] ✅ CodeQL configuration valid"
exit 0
12 changes: 9 additions & 3 deletions .githooks/validate-spdx-workflows.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
break
done < "$file"

[ "$HAS_SPDX" = false ] && {
# NOTE: this must be an `if`, not `[ ... ] && { ... }`. Under `set -e` the
# && form makes the function return 1 whenever the header IS present (the
# test is false and short-circuits), killing the script silently on VALID
# input. See scripts/tests/validate-spdx-workflows-test.sh.
if [ "$HAS_SPDX" = false ]; then

Check failure on line 25 in .githooks/validate-spdx-workflows.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Aai96SEuIT-Jpkj&open=AaCg8Aai96SEuIT-Jpkj&pullRequest=780
echo "[validate-spdx-workflows] ERROR: $file missing SPDX header" >&2
ERRORS=$((ERRORS + 1))
}
fi
}

# If staged files provided, only check those
Expand All @@ -43,6 +47,8 @@
-print 2>/dev/null)
fi

[ $ERRORS -gt 0 ] && exit 1
if [ "$ERRORS" -gt 0 ]; then

Check failure on line 50 in .githooks/validate-spdx-workflows.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Aai96SEuIT-Jpkk&open=AaCg8Aai96SEuIT-Jpkk&pullRequest=780
exit 1
fi
echo "[validate-spdx-workflows] All workflow files have SPDX headers"
exit 0
124 changes: 124 additions & 0 deletions scripts/tests/validate-codeql-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
#
# Tests for .githooks/validate-codeql.sh, the pre-commit gate.
#
# ⚠ TEST 1 IS THE REASON THIS EXISTS. The validator detected languages with
#
# HAS_RS=$(find "$SCAN_PATH" -name "*.rs" 2>/dev/null | head -1)
#
# under `set -euo pipefail`. `head` exits after one line; `find` then writes
# into a closed pipe, is killed by SIGPIPE, the pipeline reports 141, `pipefail`
# propagates that to the assignment, and `set -e` terminates the script with NO
# OUTPUT — on a perfectly valid repository. Measured: 6 of 6 runs exit 141
# against this repo, and the gate blocked every commit while printing nothing.
#
# WHAT ACTUALLY TRIGGERS IT — and the reason the first version of this test was
# vacuous. It is not the number of matches; it is whether `find` still has
# output pending once `head` is gone. A 20-file fixture in a shallow tree fits
# in the 64K pipe buffer, so find finishes writing and exits 0 and the BUGGY
# validator PASSES. The fixture below emits ~86KB of paths, exceeding the pipe
# buffer, which makes the SIGPIPE deterministic rather than a scheduling race:
# measured 5/5 exit 141 buggy, 5/5 exit 0 fixed. Any future edit that shrinks
# this fixture silently disarms the test.
#
# ⚠ WHAT THIS TEST DOES NOT CLAIM. The `[[ "$HAS_PY" ]] && ! grep -q ... && echo`
# lines were also converted to `if` blocks, but that is hygiene, NOT a bug fix:
# those lists sit at TOP LEVEL, and `set -e` does not exit on a short-circuited
# AND-OR list there (verified: `set -e; X=""; [[ "$X" ]] && echo never; echo
# SURVIVED` prints SURVIVED). The same form IS fatal as the last command of a
# function, which is the separate, genuine bug fixed in
# .githooks/validate-spdx-workflows.sh — see that test. Tests 5-7 below are
# therefore guards against regression, and they pass against the old code too.
set -uo pipefail
HOOK="$(cd "$(dirname "$0")/../.." && pwd)/.githooks/validate-codeql.sh"
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
pass=0; fail=0

ck() { # name expected_exit scan_path

Check warning on line 39 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpkr&open=AaCg8Ag_96SEuIT-Jpkr&pullRequest=780
local out rc
out="$(INPUT_PATH="$3" bash "$HOOK" 2>&1)"; rc=$?
if [ "$rc" = "$2" ]; then printf ' ok %s (exit %s)\n' "$1" "$rc"; pass=$((pass+1))

Check warning on line 42 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpkq&open=AaCg8Ag_96SEuIT-Jpkq&pullRequest=780

Check failure on line 42 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpkp&open=AaCg8Ag_96SEuIT-Jpkp&pullRequest=780
else printf ' FAIL %s (expected exit %s, got %s) output=%s\n' "$1" "$2" "$rc" "${out:-<none>}"; fail=$((fail+1)); fi
}

# mk <dir> <codeql languages literal, or NONE for no codeql.yml> <ext...>
# Deliberately long path segments and 400 directories per extension: the point
# is BYTES of find output, not file count. See the header.
mk() {

Check warning on line 49 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpkt&open=AaCg8Ag_96SEuIT-Jpkt&pullRequest=780
local d="$T/$1"; shift
local langs="$1"; shift
mkdir -p "$d/.github/workflows"
if [ "$langs" != NONE ]; then

Check failure on line 53 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpks&open=AaCg8Ag_96SEuIT-Jpks&pullRequest=780
printf "name: CodeQL\njobs:\n analyze:\n strategy:\n matrix:\n language: %s\n" "$langs" \
> "$d/.github/workflows/codeql.yml"
fi
local ext i sub
for ext in "$@"; do
for i in $(seq 1 400); do
sub="$d/src/deeply_nested_package_directory_$i/submodule_component_$i"
mkdir -p "$sub"
: > "$sub/source_file_number_$i.$ext"
: > "$sub/another_source_file_$i.$ext"
done
done
}

# Guard the guard: if the fixture stops exceeding the pipe buffer, this test
# can no longer detect the bug it exists for, and must say so loudly.
assert_fixture_big_enough() { # dir ext

Check warning on line 70 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpkv&open=AaCg8Ag_96SEuIT-Jpkv&pullRequest=780
local bytes
bytes=$(find "$1" -name "*.$2" 2>/dev/null | wc -c)
if [ "$bytes" -lt 70000 ]; then

Check failure on line 73 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpku&open=AaCg8Ag_96SEuIT-Jpku&pullRequest=780
printf ' FAIL fixture for *.%s emits only %s bytes; under the 64K pipe buffer this test CANNOT detect the SIGPIPE bug\n' "$2" "$bytes"
fail=$((fail+1))
else
printf ' ok fixture emits %s bytes of find output (> 64K pipe buffer)\n' "$bytes"
pass=$((pass+1))
fi
}

echo "[validate-codeql-test] $HOOK"

# 1. PLANTED POSITIVE — the regression. A valid repo whose find output exceeds
# the pipe buffer. Exits 141 before the fix, 0 after.
mk good "['javascript']" js
assert_fixture_big_enough "$T/good" js
ck "PLANTED POSITIVE: valid large JS repo passes" 0 "$T/good"

# 2-3. The failure was 6/6, so one green run is not evidence of a fix.
ck "PLANTED POSITIVE repeat 2" 0 "$T/good"
ck "PLANTED POSITIVE repeat 3" 0 "$T/good"

# 4. Same shape on the .rs probe, which is the line that actually died in the
# bash -x trace against this repo (HAS_RS, not HAS_JS).
mk bigrust "['actions']" rs
assert_fixture_big_enough "$T/bigrust" rs
ck "PLANTED POSITIVE: large Rust repo on ['actions'] passes" 0 "$T/bigrust"

# 5. No codeql.yml: gate not applicable, clean skip.
mk nocodeql NONE js
ck "no codeql.yml is a clean skip" 0 "$T/nocodeql"

# 6. The one real error the gate exists to raise: Rust in the CodeQL matrix.
# CodeQL has no Rust support. The fix must not disarm this.
mk rustbad "['rust']" rs
ck "Rust listed in CodeQL matrix still FAILS" 1 "$T/rustbad"

# 7. Missing-language warning must be a warning, not an error.
mk pywarn "['actions']" py
ck "missing-language warning is non-fatal" 0 "$T/pywarn"
out7="$(INPUT_PATH="$T/pywarn" bash "$HOOK" 2>&1)"
if printf '%s' "$out7" | grep -q "WARNING: Python files"; then
printf ' ok warning text is actually emitted\n'; pass=$((pass+1))
else
printf ' FAIL warning text missing; output=%s\n' "${out7:-<none>}"; fail=$((fail+1))
fi

# 8. A repo with no tracked source files at all: every guard false.
mk emptylangs "['actions']"
ck "repo with no tracked source files passes" 0 "$T/emptylangs"

printf '[validate-codeql-test] %s passed, %s failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]

Check failure on line 124 in scripts/tests/validate-codeql-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8Ag_96SEuIT-Jpkw&open=AaCg8Ag_96SEuIT-Jpkw&pullRequest=780
49 changes: 49 additions & 0 deletions scripts/tests/validate-spdx-workflows-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MPL-2.0
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
#
# Tests for .githooks/validate-spdx-workflows.sh, the pre-commit gate.
#
# ⚠ TEST 1 IS THE REASON THIS EXISTS. The validator ended validate_file() with
#
# [ "$HAS_SPDX" = false ] && { echo ERROR; ERRORS=$((ERRORS+1)); }
#
# under `set -euo pipefail`. When the header IS present that test is false, the
# && short-circuits, the function returns 1, and `set -e` killed the script —
# silently, with no output. The gate therefore exited non-zero on BOTH valid and
# invalid input: it could never pass a workflow file, and it blocked every
# workflow commit in this repo while printing nothing to say why.
#
# A gate is only proven by a PASSING case. Test 1 is that planted positive;
# without it the bug is invisible, because the failing case looked correct.
set -uo pipefail
HOOK="$(cd "$(dirname "$0")/../.." && pwd)/.githooks/validate-spdx-workflows.sh"
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
mkdir -p "$T/.github/workflows"
pass=0; fail=0

ck() { # name expected_exit staged_files

Check warning on line 25 in scripts/tests/validate-spdx-workflows-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8AhH96SEuIT-Jpkz&open=AaCg8AhH96SEuIT-Jpkz&pullRequest=780
local out rc
out="$(cd "$T" && INPUT_STAGED_FILES="$3" bash "$HOOK" 2>&1)"; rc=$?
if [ "$rc" = "$2" ]; then printf ' ok %s (exit %s)\n' "$1" "$rc"; pass=$((pass+1))

Check warning on line 28 in scripts/tests/validate-spdx-workflows-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Assign this positional parameter to a local variable.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8AhH96SEuIT-Jpky&open=AaCg8AhH96SEuIT-Jpky&pullRequest=780

Check failure on line 28 in scripts/tests/validate-spdx-workflows-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8AhH96SEuIT-Jpkx&open=AaCg8AhH96SEuIT-Jpkx&pullRequest=780
else printf ' FAIL %s (expected exit %s, got %s) output=%s\n' "$1" "$2" "$rc" "${out:-<none>}"; fail=$((fail+1)); fi
}

printf '# SPDX-License-Identifier: MPL-2.0\nname: good\non: push\n' > "$T/.github/workflows/good.yml"
printf 'name: bad\non: push\n' > "$T/.github/workflows/bad.yml"
# gh actions-lock displaces line 1; the header still sits in the leading block.
printf '# This workflow is managed by gh actions-lock.\n# SPDX-License-Identifier: MPL-2.0\nname: locked\n' \
> "$T/.github/workflows/locked.yml"

echo "validate-spdx-workflows.sh"
ck "PLANTED POSITIVE: valid header must PASS" 0 ".github/workflows/good.yml"
ck "missing header must FAIL" 1 ".github/workflows/bad.yml"
ck "header below an actions-lock line passes" 0 ".github/workflows/locked.yml"
ck "two valid files pass together" 0 ".github/workflows/good.yml
.github/workflows/locked.yml"
ck "one bad among good still fails" 1 ".github/workflows/good.yml
.github/workflows/bad.yml"
ck "non-workflow staged file is ignored" 0 "README.adoc"

printf '\n%s passed, %s failed\n' "$pass" "$fail"
[ "$fail" -eq 0 ]

Check failure on line 49 in scripts/tests/validate-spdx-workflows-test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=hyperpolymath_standards&issues=AaCg8AhH96SEuIT-Jpk0&open=AaCg8AhH96SEuIT-Jpk0&pullRequest=780
Loading