From 827c095d28e630b454241aa7a5a5db5bde873568 Mon Sep 17 00:00:00 2001 From: "John E. Malmberg" Date: Tue, 9 Jun 2026 10:01:12 -0500 Subject: [PATCH 1/3] SRE-3850: fix @Library guard test check Fix syntax error in the guard script that caused it to fail. Add unit tests for the guard to prevent regressions. - checks/guard-jenkins-library-pin.sh: Replace \( with [(] in the library_regex variable. - tests/guard-jenkins-library-pin-test.sh: New file. Seven-case unit test for the guard script. - .github/workflows/checks.yml: Add unit test step for the guard script. Signed-off-by: John E. Malmberg --- .github/workflows/checks.yml | 3 + checks/guard-jenkins-library-pin.sh | 2 +- tests/guard-jenkins-library-pin-test.sh | 274 ++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100755 tests/guard-jenkins-library-pin-test.sh diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index acbd87e..81982df 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -23,6 +23,9 @@ jobs: - name: Block tracked code-checking-ref run: ./checks/guard-code-checking-ref.sh --target-root . + - name: Unit test Jenkins @Library guard (SRE-3850) + run: bash ./tests/guard-jenkins-library-pin-test.sh + - name: Block active Jenkins @Library reference run: ./checks/guard-jenkins-library-pin.sh --target-root . diff --git a/checks/guard-jenkins-library-pin.sh b/checks/guard-jenkins-library-pin.sh index 2f54ed7..e79167e 100755 --- a/checks/guard-jenkins-library-pin.sh +++ b/checks/guard-jenkins-library-pin.sh @@ -58,7 +58,7 @@ if [[ ${#JENKINS_FILES[@]} -eq 0 ]]; then exit 0 fi -library_regex='@Library[[:space:]]*\(' +library_regex='@Library[[:space:]]*[(]' guard_failed=0 for rel_path in "${JENKINS_FILES[@]}"; do diff --git a/tests/guard-jenkins-library-pin-test.sh b/tests/guard-jenkins-library-pin-test.sh new file mode 100755 index 0000000..ac95d8a --- /dev/null +++ b/tests/guard-jenkins-library-pin-test.sh @@ -0,0 +1,274 @@ +#!/usr/bin/env bash +# Copyright 2026 Hewlett Packard Enterprise Development LP +set -euo pipefail + +# Unit tests for checks/guard-jenkins-library-pin.sh +# +# SRE-3850: validates the awk regex portability fix for @Library pattern +# matching. Each case builds a minimal temporary git repository with a +# fixture Jenkinsfile, runs the guard, and verifies: +# - expected exit code +# - expected stdout/stderr content +# - absence of the SRE-3850 awk regression (warning/fatal on stderr) +# +# Usage: bash tests/guard-jenkins-library-pin-test.sh + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +GUARD="${REPO_ROOT}/checks/guard-jenkins-library-pin.sh" + +pass_count=0 +fail_count=0 + +WORK_DIR="" +cleanup_all() { + if [[ -n "${WORK_DIR}" ]]; then + # git writes object files read-only; restore write permission before removal. + chmod -R u+w "${WORK_DIR}" 2>/dev/null || true + rm -rf "${WORK_DIR}" 2>/dev/null || true + fi +} +trap cleanup_all EXIT + +WORK_DIR="$(mktemp -d)" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +pass() { echo "[PASS] $1"; pass_count=$(( pass_count + 1 )); } +fail() { echo "[FAIL] $1" >&2; fail_count=$(( fail_count + 1 )); } + +# make_repo DIR +# Initialize a throwaway git repo with a committed state ready for ls-files. +make_repo() { + local dir="$1" + mkdir -p "${dir}" + git -C "${dir}" init -q + git -C "${dir}" config user.email "test@test.local" + git -C "${dir}" config user.name "Test" +} + +# commit_file REPO_DIR REL_PATH +# Stage and commit the file at REL_PATH inside REPO_DIR. +commit_file() { + local dir="$1" + local rel="$2" + git -C "${dir}" add "${rel}" + git -C "${dir}" commit -qm "test fixture: ${rel}" +} + +# run_case NAME WORKDIR EXPECTED_EXIT [STDOUT_SUBSTR] [STDERR_SUBSTR] +# Runs the guard and reports pass/fail. Always checks for the SRE-3850 +# awk portability regression regardless of other assertions. +run_case() { + local name="$1" + local workdir="$2" + local expected_exit="$3" + local stdout_substr="${4:-}" + local stderr_substr="${5:-}" + + local out_file="${WORK_DIR}/${name}.out" + local err_file="${WORK_DIR}/${name}.err" + local actual_exit=0 + + "${GUARD}" --target-root "${workdir}" \ + >"${out_file}" 2>"${err_file}" || actual_exit=$? + + local ok=1 + + # --- exit code check --- + if [[ "${actual_exit}" -ne "${expected_exit}" ]]; then + echo " [${name}] expected exit ${expected_exit}, got ${actual_exit}" >&2 + echo " stdout: $(cat "${out_file}")" >&2 + echo " stderr: $(cat "${err_file}")" >&2 + ok=0 + fi + + # --- stdout content check --- + if [[ -n "${stdout_substr}" ]] && \ + ! grep -qF "${stdout_substr}" "${out_file}"; then + echo " [${name}] stdout missing: ${stdout_substr}" >&2 + echo " stdout was: $(cat "${out_file}")" >&2 + ok=0 + fi + + # --- stderr content check --- + if [[ -n "${stderr_substr}" ]] && \ + ! grep -qF "${stderr_substr}" "${err_file}"; then + echo " [${name}] stderr missing: ${stderr_substr}" >&2 + echo " stderr was: $(cat "${err_file}")" >&2 + ok=0 + fi + + # --- SRE-3850 regression guard: no awk warning/fatal on stderr --- + if grep -qE \ + 'awk.*escape sequence|fatal.*invalid regexp|Unmatched' \ + "${err_file}" 2>/dev/null; then + echo " [${name}] SRE-3850 awk regexp regression detected:" >&2 + grep -E 'awk.*escape sequence|fatal.*invalid regexp|Unmatched' \ + "${err_file}" >&2 + ok=0 + fi + + if [[ "${ok}" -eq 1 ]]; then + pass "${name}" + else + fail "${name}" + fi +} + +# --------------------------------------------------------------------------- +# Case 1 — no Jenkinsfile in repo: guard exits 0, reports no candidates +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_no_jenkinsfile" + make_repo "${d}" + touch "${d}/README.md" + commit_file "${d}" README.md + + run_case \ + "no_jenkinsfile" "${d}" 0 \ + "no Jenkinsfile candidates found" +} + +# --------------------------------------------------------------------------- +# Case 2 — Jenkinsfile with no @Library: guard exits 0, reports ok +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_clean" + make_repo "${d}" + cat > "${d}/Jenkinsfile" <<'GROOVY' +pipeline { + agent any + stages { + stage('build') { steps { sh 'make' } } + } +} +GROOVY + commit_file "${d}" Jenkinsfile + + run_case \ + "clean_jenkinsfile" "${d}" 0 \ + "no active @Library references found" +} + +# --------------------------------------------------------------------------- +# Case 3 — active @Library reference: guard exits 1, reports blocked +# This is the primary SRE-3850 regression case; the awk regex must not +# crash before it has a chance to detect the pattern. +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_active_library" + make_repo "${d}" + cat > "${d}/Jenkinsfile" <<'GROOVY' +@Library("my-shared-lib") _ +pipeline { + agent any + stages { + stage('build') { steps { sh 'make' } } + } +} +GROOVY + commit_file "${d}" Jenkinsfile + + run_case \ + "active_library_blocked" "${d}" 1 \ + "" \ + "blocked: active @Library reference" +} + +# --------------------------------------------------------------------------- +# Case 4 — @Library in a // line comment: must be ignored, guard exits 0 +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_line_comment" + make_repo "${d}" + cat > "${d}/Jenkinsfile" <<'GROOVY' +// @Library("my-shared-lib") _ +pipeline { + agent any + stages { + stage('build') { steps { sh 'make' } } + } +} +GROOVY + commit_file "${d}" Jenkinsfile + + run_case \ + "library_in_line_comment_ignored" "${d}" 0 \ + "no active @Library references found" +} + +# --------------------------------------------------------------------------- +# Case 5 — @Library inside a /* */ block comment: must be ignored, exits 0 +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_block_comment" + make_repo "${d}" + cat > "${d}/Jenkinsfile" <<'GROOVY' +/* + * Example usage: @Library("my-shared-lib") _ + */ +pipeline { + agent any + stages { + stage('build') { steps { sh 'make' } } + } +} +GROOVY + commit_file "${d}" Jenkinsfile + + run_case \ + "library_in_block_comment_ignored" "${d}" 0 \ + "no active @Library references found" +} + +# --------------------------------------------------------------------------- +# Case 6 — @Library with whitespace before paren (spacing variant): blocked +# Validates that [[:space:]]* in the pattern still matches after the portability fix. +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_spaced_paren" + make_repo "${d}" + cat > "${d}/Jenkinsfile" <<'GROOVY' +@Library ("my-shared-lib") _ +pipeline { + agent any +} +GROOVY + commit_file "${d}" Jenkinsfile + + run_case \ + "library_spaced_paren_blocked" "${d}" 1 \ + "" \ + "blocked: active @Library reference" +} + +# --------------------------------------------------------------------------- +# Case 7 — Jenkinsfile in a subdirectory: guard must detect it +# --------------------------------------------------------------------------- +{ + d="${WORK_DIR}/case_subdir" + make_repo "${d}" + mkdir -p "${d}/jobs" + cat > "${d}/jobs/Jenkinsfile" <<'GROOVY' +@Library("my-shared-lib") _ +pipeline { agent any } +GROOVY + commit_file "${d}" jobs/Jenkinsfile + + run_case \ + "active_library_in_subdir_blocked" "${d}" 1 \ + "" \ + "blocked: active @Library reference" +} + +# --------------------------------------------------------------------------- +# Summary +# --------------------------------------------------------------------------- +echo "" +echo "Results: ${pass_count} passed, ${fail_count} failed" + +if [[ "${fail_count}" -ne 0 ]]; then + exit 1 +fi From d3c4aa076abf70764467fe321d322ddbc5fbeb28 Mon Sep 17 00:00:00 2001 From: Jan Michalski Date: Tue, 9 Jun 2026 17:54:31 +0000 Subject: [PATCH 2/3] SRE-3850 checks: break pin guard Signed-off-by: Jan Michalski --- checks/guard-jenkins-library-pin.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checks/guard-jenkins-library-pin.sh b/checks/guard-jenkins-library-pin.sh index e79167e..2f54ed7 100755 --- a/checks/guard-jenkins-library-pin.sh +++ b/checks/guard-jenkins-library-pin.sh @@ -58,7 +58,7 @@ if [[ ${#JENKINS_FILES[@]} -eq 0 ]]; then exit 0 fi -library_regex='@Library[[:space:]]*[(]' +library_regex='@Library[[:space:]]*\(' guard_failed=0 for rel_path in "${JENKINS_FILES[@]}"; do From f71a7ba9cff9d9d4c931b9201522b996f84bfd57 Mon Sep 17 00:00:00 2001 From: Jan Michalski Date: Wed, 10 Jun 2026 23:52:32 +0000 Subject: [PATCH 3/3] SRE-3850 test: remove SRE-3850 guard Signed-off-by: Jan Michalski --- tests/guard-jenkins-library-pin-test.sh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/guard-jenkins-library-pin-test.sh b/tests/guard-jenkins-library-pin-test.sh index ac95d8a..7909fe9 100755 --- a/tests/guard-jenkins-library-pin-test.sh +++ b/tests/guard-jenkins-library-pin-test.sh @@ -100,16 +100,6 @@ run_case() { ok=0 fi - # --- SRE-3850 regression guard: no awk warning/fatal on stderr --- - if grep -qE \ - 'awk.*escape sequence|fatal.*invalid regexp|Unmatched' \ - "${err_file}" 2>/dev/null; then - echo " [${name}] SRE-3850 awk regexp regression detected:" >&2 - grep -E 'awk.*escape sequence|fatal.*invalid regexp|Unmatched' \ - "${err_file}" >&2 - ok=0 - fi - if [[ "${ok}" -eq 1 ]]; then pass "${name}" else