diff --git a/.gitignore b/.gitignore index db50f6fd8..9870f5286 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .ralph tasks + +# Audit output directories (UTC timestamp format) +vibe-code-audit/[0-9]*T[0-9]*Z/ diff --git a/install.sh b/install.sh index 88eca2fdc..314732ace 100755 --- a/install.sh +++ b/install.sh @@ -1,6 +1,11 @@ #!/bin/sh set -eu +# NOTE: This is a standalone POSIX sh installer that does NOT source _lib.sh. +# It intentionally defines its own output helpers (info, dim, ok, warn, die) +# for colored terminal output. Changes to logging in _lib.sh must be +# manually reconciled here if the installer's output contract changes. + SKILL_NAME="vibe-code-audit" REPO_OWNER="${VIBE_CODE_AUDIT_REPO_OWNER:-codesoda}" REPO_NAME="${VIBE_CODE_AUDIT_REPO_NAME:-vibe-code-audit}" diff --git a/tests/_test_lib.sh b/tests/_test_lib.sh new file mode 100644 index 000000000..8633108ae --- /dev/null +++ b/tests/_test_lib.sh @@ -0,0 +1,153 @@ +# shellcheck shell=bash +# _test_lib.sh — shared test harness for vibe-code-audit test suite +# +# Sourced by all test files. Provides: +# - pass()/fail() counter functions +# - assert_eq() helper +# - setup_tmproot() / cleanup_tmproot() for temp dir lifecycle +# - print_results() for uniform summary output +# - build_filtered_path() for hiding commands during testing +# - assert_no_crash_diagnostics() for crash pattern detection +# - Automatic cleanup trap registration +# +# The sourcing script MUST: +# 1. Set `set -euo pipefail` before sourcing. +# 2. Optionally set TEST_NAME before sourcing (used in output prefix). +# +# Optional modes: +# FILE_COUNTERS=1 — use file-based pass/fail counters (safe in subshells). +# Requires setup_tmproot() to be called first. + +TEST_NAME="${TEST_NAME:-$(basename "$0" .sh)}" +# shellcheck disable=SC2034 # ROOT_DIR is used by sourcing scripts +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" + +PASS=0 +FAIL=0 + +# File-based counter support (for subshell-safe counting) +FILE_COUNTERS="${FILE_COUNTERS:-0}" +_PASS_FILE="" +_FAIL_FILE="" + +_init_file_counters() { + if [ "$FILE_COUNTERS" -eq 1 ] && [ -n "${TMPROOT:-}" ]; then + _PASS_FILE="$TMPROOT/.pass_count" + _FAIL_FILE="$TMPROOT/.fail_count" + printf '0\n' > "$_PASS_FILE" + printf '0\n' > "$_FAIL_FILE" + fi +} + +pass() { + if [ "$FILE_COUNTERS" -eq 1 ] && [ -n "$_PASS_FILE" ]; then + local c + c="$(cat "$_PASS_FILE")" + printf '%d\n' "$((c + 1))" > "$_PASS_FILE" + else + PASS=$((PASS + 1)) + fi + printf 'PASS: %s\n' "$*" +} + +fail() { + if [ "$FILE_COUNTERS" -eq 1 ] && [ -n "$_FAIL_FILE" ]; then + local c + c="$(cat "$_FAIL_FILE")" + printf '%d\n' "$((c + 1))" > "$_FAIL_FILE" + else + FAIL=$((FAIL + 1)) + fi + printf 'FAIL: %s\n' "$*" >&2 +} + +# assert_eq LABEL EXPECTED ACTUAL +assert_eq() { + local label="$1" expected="$2" actual="$3" + if [ "$expected" = "$actual" ]; then + pass "$label" + else + fail "$label" + printf ' expected: %s\n' "$expected" >&2 + printf ' actual: %s\n' "$actual" >&2 + fi +} + +# --------------------------------------------------------------------------- +# Temp directory lifecycle +# --------------------------------------------------------------------------- + +TMPROOT="" + +setup_tmproot() { + TMPROOT="$(mktemp -d "${TMPDIR:-/tmp}/vca-test-${TEST_NAME}.XXXXXX")" + if [ "$FILE_COUNTERS" -eq 1 ]; then + _init_file_counters + fi +} + +cleanup_tmproot() { + if [ -n "$TMPROOT" ] && [ -d "$TMPROOT" ]; then + rm -rf "$TMPROOT" + fi +} + +trap cleanup_tmproot EXIT INT TERM + +# --------------------------------------------------------------------------- +# PATH manipulation helpers +# --------------------------------------------------------------------------- + +# build_filtered_path COMMAND +# Builds a colon-separated PATH string with directories containing COMMAND +# removed. Result is stored in FILTERED_PATH. +build_filtered_path() { + local cmd_to_hide="$1" + FILTERED_PATH="" + local segment + IFS=':' + for segment in $PATH; do + if [ -z "$segment" ]; then + continue + fi + if [ -x "$segment/$cmd_to_hide" ]; then + continue + fi + if [ -n "$FILTERED_PATH" ]; then + FILTERED_PATH="$FILTERED_PATH:$segment" + else + FILTERED_PATH="$segment" + fi + done + unset IFS +} + +# --------------------------------------------------------------------------- +# Assertion helpers +# --------------------------------------------------------------------------- + +# assert_no_crash_diagnostics CONTENT +# Fails if CONTENT contains shell crash patterns (unbound variable, syntax +# error, segfault, core dump, panic). +assert_no_crash_diagnostics() { + local content="$1" + if echo "$content" | grep -Eiq 'unbound variable|syntax error|segmentation fault|core dumped|panic'; then + fail "stderr contains crash diagnostic: $(echo "$content" | grep -Ei 'unbound variable|syntax error|segmentation fault|core dumped|panic' | head -1)" + else + pass "no crash diagnostics in stderr" + fi +} + +# --------------------------------------------------------------------------- +# Results summary +# --------------------------------------------------------------------------- + +print_results() { + # Sync from file counters if active + if [ "$FILE_COUNTERS" -eq 1 ] && [ -n "$_PASS_FILE" ] && [ -f "$_PASS_FILE" ]; then + PASS="$(cat "$_PASS_FILE")" + FAIL="$(cat "$_FAIL_FILE")" + fi + printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" + [ "$FAIL" -eq 0 ] +} diff --git a/tests/atomic_index_midrun_failure_test.sh b/tests/atomic_index_midrun_failure_test.sh index a7fb7744c..007f9a214 100644 --- a/tests/atomic_index_midrun_failure_test.sh +++ b/tests/atomic_index_midrun_failure_test.sh @@ -8,37 +8,14 @@ set -euo pipefail # - Any prior audit_index/ is untouched # - After a successful run, audit_index/ has fresh content and audit_index.tmp/ is absent -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -RUN_INDEX_SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/run_index.sh" - -TEST_TMPDIR="" -cleanup() { - if [ -n "$TEST_TMPDIR" ] && [ -d "$TEST_TMPDIR" ]; then - rm -rf "$TEST_TMPDIR" - fi -} -trap cleanup EXIT INT TERM - -TEST_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/vca-atomic-idx-test.XXXXXX")" +TEST_NAME="atomic_index_midrun" +FILE_COUNTERS=1 +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -PASS_FILE="$TEST_TMPDIR/.pass_count" -FAIL_FILE="$TEST_TMPDIR/.fail_count" -printf '0\n' > "$PASS_FILE" -printf '0\n' > "$FAIL_FILE" +RUN_INDEX_SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/run_index.sh" -pass() { - local c - c="$(cat "$PASS_FILE")" - printf '%d\n' "$((c + 1))" > "$PASS_FILE" - printf '[atomic_index_midrun] PASS: %s\n' "$1" -} - -fail() { - local c - c="$(cat "$FAIL_FILE")" - printf '%d\n' "$((c + 1))" > "$FAIL_FILE" - printf '[atomic_index_midrun] FAIL: %s\n' "$1" >&2 -} +setup_tmproot # --- Helper: create mock binaries --- @@ -178,7 +155,7 @@ EOF ( set -euo pipefail - work_dir="$TEST_TMPDIR/case-failure" + work_dir="$TMPROOT/case-failure" repo_dir="$work_dir/repo" output_dir="$work_dir/output" bin_dir="$work_dir/bin" @@ -245,7 +222,7 @@ EOF ( set -euo pipefail - work_dir="$TEST_TMPDIR/case-success" + work_dir="$TMPROOT/case-success" repo_dir="$work_dir/repo" output_dir="$work_dir/output" bin_dir="$work_dir/bin" @@ -318,7 +295,7 @@ EOF ( set -euo pipefail - work_dir="$TEST_TMPDIR/case-success-replace" + work_dir="$TMPROOT/case-success-replace" repo_dir="$work_dir/repo" output_dir="$work_dir/output" bin_dir="$work_dir/bin" @@ -373,9 +350,4 @@ EOF # Summary # ============================================================ -PASS="$(cat "$PASS_FILE")" -FAIL="$(cat "$FAIL_FILE")" -printf '\n[atomic_index_midrun] Results: %d passed, %d failed\n' "$PASS" "$FAIL" -if [ "$FAIL" -gt 0 ]; then - exit 1 -fi +print_results diff --git a/tests/embed_env_hardening_test.sh b/tests/embed_env_hardening_test.sh index 87f9114ce..a2ca08cd5 100644 --- a/tests/embed_env_hardening_test.sh +++ b/tests/embed_env_hardening_test.sh @@ -1,12 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -PASS=0 -FAIL=0 -fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } -pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } +TEST_NAME="embed_env_hardening" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -SCRIPTS_DIR="$(cd "$(dirname "$0")/../vibe-code-audit/scripts" && pwd)" +SCRIPTS_DIR="$ROOT_DIR/vibe-code-audit/scripts" SCRIPT="$SCRIPTS_DIR/run_agentroot_embed.sh" echo "=== Embed Env Hardening Tests ===" @@ -56,12 +55,10 @@ fi # --- Dynamic test infrastructure --- -TMPDIR_ROOT="$(mktemp -d)" -cleanup() { rm -rf "$TMPDIR_ROOT"; } -trap cleanup EXIT INT TERM +setup_tmproot # Create mock binaries -MOCK_BIN="$TMPDIR_ROOT/bin" +MOCK_BIN="$TMPROOT/bin" mkdir -p "$MOCK_BIN" # Mock agentroot: always fails embed (triggers connection-refused path) @@ -93,7 +90,7 @@ run_with_env() { shift local tag="${1:-default}" shift || true - local test_home="$TMPDIR_ROOT/home_${tag}" + local test_home="$TMPROOT/home_${tag}" mkdir -p "$test_home/.config/vibe-code-audit" local test_db="$test_home/test.sqlite" touch "$test_db" @@ -108,7 +105,7 @@ run_with_env() { # Helper: get the health URL the script tried (via mock curl log) get_health_url() { local tag="$1" - local curl_log="$TMPDIR_ROOT/home_${tag}/curl_urls.log" + local curl_log="$TMPROOT/home_${tag}/curl_urls.log" if [ -f "$curl_log" ]; then head -1 "$curl_log" else @@ -143,7 +140,7 @@ fi # --- 7. Command injection via semicolon is NOT executed --- echo "" echo "--- Dynamic: command injection prevention ---" -PWNED="$TMPDIR_ROOT/pwned_semicolon" +PWNED="$TMPROOT/pwned_semicolon" run_with_env "VIBE_CODE_AUDIT_EMBED_HOST=localhost; touch $PWNED" "inject_semi" >/dev/null if [ -f "$PWNED" ]; then fail "Command injection via semicolon was executed" @@ -152,7 +149,7 @@ else fi # --- 8. Command substitution is NOT executed --- -PWNED2="$TMPDIR_ROOT/pwned_subst" +PWNED2="$TMPROOT/pwned_subst" run_with_env "VIBE_CODE_AUDIT_EMBED_HOST=\$(touch $PWNED2)" "inject_subst" >/dev/null if [ -f "$PWNED2" ]; then fail "Command substitution injection was executed" @@ -161,7 +158,7 @@ else fi # --- 9. Backtick injection is NOT executed --- -PWNED3="$TMPDIR_ROOT/pwned_backtick" +PWNED3="$TMPROOT/pwned_backtick" run_with_env "VIBE_CODE_AUDIT_EMBED_HOST=\`touch $PWNED3\`" "inject_backtick" >/dev/null if [ -f "$PWNED3" ]; then fail "Backtick injection was executed" @@ -174,7 +171,7 @@ echo "" echo "--- Dynamic: non-whitelisted keys ---" # If non-whitelisted keys leaked, PATH would be overwritten and agentroot wouldn't be found # The script would die with "agentroot is not installed" instead of producing EMBED_OK output -test_home_nwl="$TMPDIR_ROOT/home_nwl" +test_home_nwl="$TMPROOT/home_nwl" mkdir -p "$test_home_nwl/.config/vibe-code-audit" touch "$test_home_nwl/test.sqlite" printf 'EVIL_KEY=drop_tables\nLD_PRELOAD=/evil.so\nVIBE_CODE_AUDIT_EMBED_HOST=5.5.5.5\n' \ @@ -213,7 +210,7 @@ fi # --- 12. Missing embed.env does not crash --- echo "" echo "--- Dynamic: missing embed.env ---" -test_home_miss="$TMPDIR_ROOT/home_miss" +test_home_miss="$TMPROOT/home_miss" mkdir -p "$test_home_miss" touch "$test_home_miss/test.sqlite" miss_output="$(HOME="$test_home_miss" PATH="$MOCK_BIN:/usr/bin:/bin:/usr/sbin:/sbin" \ @@ -239,7 +236,7 @@ fi # --- 14. CLI flags override embed.env values --- echo "" echo "--- Dynamic: CLI override precedence ---" -test_home_cli="$TMPDIR_ROOT/home_cli" +test_home_cli="$TMPROOT/home_cli" mkdir -p "$test_home_cli/.config/vibe-code-audit" touch "$test_home_cli/test.sqlite" printf 'VIBE_CODE_AUDIT_EMBED_HOST=from-file\nVIBE_CODE_AUDIT_EMBED_PORT=1111\n' \ @@ -258,7 +255,7 @@ fi # --- 14b. Pre-existing env var takes precedence over embed.env --- echo "" echo "--- Dynamic: environment variable precedence over embed.env ---" -test_home_prec="$TMPDIR_ROOT/home_prec" +test_home_prec="$TMPROOT/home_prec" mkdir -p "$test_home_prec/.config/vibe-code-audit" touch "$test_home_prec/test.sqlite" printf 'VIBE_CODE_AUDIT_EMBED_HOST=from-file\nVIBE_CODE_AUDIT_EMBED_PORT=1111\n' \ @@ -291,8 +288,8 @@ echo "" echo "--- Dynamic: end-to-end output structure ---" run_with_env 'VIBE_CODE_AUDIT_EMBED_HOST=127.0.0.1 VIBE_CODE_AUDIT_EMBED_PORT=8000' "e2e" >/dev/null -e2e_output="$(HOME="$TMPDIR_ROOT/home_e2e" PATH="$MOCK_BIN:/usr/bin:/bin:/usr/sbin:/sbin" \ - bash "$SCRIPT" --db "$TMPDIR_ROOT/home_e2e/test.sqlite" --no-start-local 2>/dev/null || true)" +e2e_output="$(HOME="$TMPROOT/home_e2e" PATH="$MOCK_BIN:/usr/bin:/bin:/usr/sbin:/sbin" \ + bash "$SCRIPT" --db "$TMPROOT/home_e2e/test.sqlite" --no-start-local 2>/dev/null || true)" if echo "$e2e_output" | grep -q 'EMBED_OK=' && \ echo "$e2e_output" | grep -q 'EMBED_BACKEND='; then pass "End-to-end output contains EMBED_OK and EMBED_BACKEND" @@ -304,7 +301,7 @@ fi echo "" echo "--- Dynamic: env snapshot proves forbidden keys excluded ---" # Create an env-snapshot mock agentroot that dumps its inherited environment -SNAP_BIN="$TMPDIR_ROOT/snap_bin" +SNAP_BIN="$TMPROOT/snap_bin" mkdir -p "$SNAP_BIN" cat > "$SNAP_BIN/agentroot" <<'SNAP_STUB' #!/usr/bin/env bash @@ -319,7 +316,7 @@ chmod +x "$SNAP_BIN/agentroot" # Reuse the existing mock curl in snap_bin cp "$MOCK_BIN/curl" "$SNAP_BIN/curl" -test_home_snap="$TMPDIR_ROOT/home_snap" +test_home_snap="$TMPROOT/home_snap" mkdir -p "$test_home_snap/.config/vibe-code-audit" touch "$test_home_snap/test.sqlite" SNAP_LOG="$test_home_snap/agentroot_env.log" @@ -353,7 +350,7 @@ fi # --- 18. Combined injection + forbidden key payload --- echo "" echo "--- Dynamic: combined injection and forbidden key payload ---" -PWNED_COMBINED="$TMPDIR_ROOT/pwned_combined" +PWNED_COMBINED="$TMPROOT/pwned_combined" SNAP_LOG2="$test_home_snap/agentroot_env2.log" printf 'VIBE_CODE_AUDIT_EMBED_HOST=safe-host\nEVIL_KEY=should_be_ignored\nVIBE_CODE_AUDIT_EMBED_HOST=localhost; touch %s\n$(touch %s)\n' \ "$PWNED_COMBINED" "$PWNED_COMBINED" \ @@ -378,13 +375,11 @@ fi # --- 19. Temp file leak check --- echo "" echo "--- Cleanup: temp file leak check ---" -leaked_files="$(find "$TMPDIR_ROOT" -name 'vca-*' -type f 2>/dev/null || true)" +leaked_files="$(find "$TMPROOT" -name 'vca-*' -type f 2>/dev/null || true)" if [ -z "$leaked_files" ]; then pass "No vca-* temp file leaks under test root" else fail "Leaked temp files found: $leaked_files" fi -echo "" -echo "=== Results: $PASS passed, $FAIL failed ===" -[ "$FAIL" -eq 0 ] || exit 1 +print_results diff --git a/tests/empty_repo_test.sh b/tests/empty_repo_test.sh index ef2951e4f..b696cf909 100644 --- a/tests/empty_repo_test.sh +++ b/tests/empty_repo_test.sh @@ -8,36 +8,17 @@ set -euo pipefail # 3. Valid hotspots.json with empty files_by_symbol_count # 4. Non-empty dup_clusters.md bootstrap scaffold -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/build_derived_artifacts.sh" - -PASS=0 -FAIL=0 +TEST_NAME="empty_repo" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -fail() { - printf 'FAIL: %s\n' "$*" >&2 - FAIL=$((FAIL + 1)) -} - -pass() { - printf 'PASS: %s\n' "$*" - PASS=$((PASS + 1)) -} +SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/build_derived_artifacts.sh" # --------------------------------------------------------------------------- # Setup: temp fixture with empty repo, cleanup trap # --------------------------------------------------------------------------- -TMPROOT="" - -cleanup() { - if [ -n "$TMPROOT" ] && [ -d "$TMPROOT" ]; then - rm -rf "$TMPROOT" - fi -} -trap cleanup EXIT INT TERM - -TMPROOT="$(mktemp -d)" +setup_tmproot MOCK_REPO="$TMPROOT/repo" OUTPUT_DIR="$TMPROOT/output" mkdir -p "$MOCK_REPO/.git" "$OUTPUT_DIR" @@ -241,5 +222,4 @@ fi # Summary # --------------------------------------------------------------------------- -printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" -[ "$FAIL" -eq 0 ] || exit 1 +print_results diff --git a/tests/grep_fallback_test.sh b/tests/grep_fallback_test.sh index 8e2bbe4d4..39fc64731 100644 --- a/tests/grep_fallback_test.sh +++ b/tests/grep_fallback_test.sh @@ -7,38 +7,18 @@ set -euo pipefail # 2. Produces correct read_plan.tsv / read_plan.md artifacts # 3. Excludes files from excluded directories and includes files from valid paths -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/build_read_plan.sh" - -PASS=0 -FAIL=0 - -fail() { - printf 'FAIL: %s\n' "$*" >&2 - FAIL=$((FAIL + 1)) -} +TEST_NAME="grep_fallback" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -pass() { - printf 'PASS: %s\n' "$*" - PASS=$((PASS + 1)) -} +SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/build_read_plan.sh" # --------------------------------------------------------------------------- # Setup: temp dirs, PATH hiding, cleanup trap # --------------------------------------------------------------------------- ORIG_PATH="$PATH" -TMPROOT="" - -cleanup() { - PATH="$ORIG_PATH" - if [ -n "$TMPROOT" ] && [ -d "$TMPROOT" ]; then - rm -rf "$TMPROOT" - fi -} -trap cleanup EXIT INT TERM - -TMPROOT="$(mktemp -d)" +setup_tmproot MOCK_REPO="$TMPROOT/repo" OUTPUT_DIR="$TMPROOT/output" mkdir -p "$MOCK_REPO" "$OUTPUT_DIR" @@ -100,21 +80,7 @@ printf 'const timeout = config.retryBackoff || 3000;\n' > "$MOCK_REPO/lib/retry. # PATH manipulation: hide rg # --------------------------------------------------------------------------- -# Build a filtered PATH that excludes any directory containing rg -FILTERED_PATH="" -IFS=':' -for segment in $ORIG_PATH; do - if [ -x "$segment/rg" ]; then - continue - fi - if [ -n "$FILTERED_PATH" ]; then - FILTERED_PATH="$FILTERED_PATH:$segment" - else - FILTERED_PATH="$segment" - fi -done -unset IFS - +build_filtered_path "rg" export PATH="$FILTERED_PATH" # Verify rg is truly hidden @@ -122,7 +88,7 @@ if command -v rg >/dev/null 2>&1; then fail "rg still visible in PATH after filtering — fallback test unreliable" # Restore and skip dynamic tests PATH="$ORIG_PATH" - printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" + print_results exit "$FAIL" else pass "rg hidden from PATH — grep fallback will be exercised" @@ -243,5 +209,4 @@ fi # Summary # --------------------------------------------------------------------------- -printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" -[ "$FAIL" -eq 0 ] || exit 1 +print_results diff --git a/tests/lib_unit_test.sh b/tests/lib_unit_test.sh index 0472fc5e5..994cd62fb 100755 --- a/tests/lib_unit_test.sh +++ b/tests/lib_unit_test.sh @@ -1,34 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -LIB_SH="$ROOT_DIR/vibe-code-audit/scripts/_lib.sh" - -PASS=0 -FAIL=0 - -fail() { - printf 'FAIL: %s\n' "$*" >&2 - FAIL=$((FAIL + 1)) -} - -pass() { - printf 'PASS: %s\n' "$*" - PASS=$((PASS + 1)) -} +TEST_NAME="lib_unit_test" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -assert_eq() { - local label="$1" - local expected="$2" - local actual="$3" - if [ "$expected" = "$actual" ]; then - pass "$label" - else - fail "$label" - printf ' expected: %s\n' "$expected" >&2 - printf ' actual: %s\n' "$actual" >&2 - fi -} +LIB_SH="$ROOT_DIR/vibe-code-audit/scripts/_lib.sh" # Source _lib.sh (requires SCRIPT_NAME) SCRIPT_NAME="lib_unit_test" @@ -443,10 +420,25 @@ else pass "resolve_output_dir unresolvable path exits non-zero" fi +# =========================================================================== +# EMBED_ENV_FILE constant tests +# =========================================================================== + +# --------------------------------------------------------------------------- +# Test: EMBED_ENV_FILE has correct default value +# --------------------------------------------------------------------------- +assert_eq "EMBED_ENV_FILE default value" "$HOME/.config/vibe-code-audit/embed.env" "$EMBED_ENV_FILE" + +# --------------------------------------------------------------------------- +# Test: EMBED_ENV_FILE can be overridden via environment +# --------------------------------------------------------------------------- +EMBED_ENV_FILE="/tmp/custom/embed.env" +# Re-source to verify the variable respects environment override +SCRIPT_NAME="lib_unit_test" +. "$LIB_SH" +assert_eq "EMBED_ENV_FILE override" "/tmp/custom/embed.env" "$EMBED_ENV_FILE" + # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- -printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" -if [ "$FAIL" -gt 0 ]; then - exit 1 -fi +print_results diff --git a/tests/manifest_integrity_test.sh b/tests/manifest_integrity_test.sh index b1fc00426..21b9c4057 100644 --- a/tests/manifest_integrity_test.sh +++ b/tests/manifest_integrity_test.sh @@ -4,21 +4,18 @@ set -euo pipefail # Manifest integrity test for INSTALL_MANIFEST.txt # Ensures _lib.sh is listed exactly once and no duplicate entries exist. -MANIFEST="vibe-code-audit/INSTALL_MANIFEST.txt" -PASS=0 -FAIL=0 - -pass() { PASS=$((PASS + 1)); printf " PASS: %s\n" "$1"; } -fail() { FAIL=$((FAIL + 1)); printf " FAIL: %s\n" "$1"; } +TEST_NAME="manifest_integrity" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -printf "=== Manifest Integrity Tests ===\n" +MANIFEST="vibe-code-audit/INSTALL_MANIFEST.txt" # 1. Manifest file exists if [ -f "$MANIFEST" ]; then pass "manifest file exists" else fail "manifest file not found at $MANIFEST" - printf "\nResults: %d passed, %d failed\n" "$PASS" "$FAIL" + print_results exit 1 fi @@ -60,5 +57,4 @@ else fail "entry format mismatch: got '$LIB_LINE'" fi -printf "\n=== Results: %d passed, %d failed ===\n" "$PASS" "$FAIL" -[ "$FAIL" -eq 0 ] || exit 1 +print_results diff --git a/tests/render_pdf_smoke_test.sh b/tests/render_pdf_smoke_test.sh index 417307dd7..9ba866d04 100644 --- a/tests/render_pdf_smoke_test.sh +++ b/tests/render_pdf_smoke_test.sh @@ -7,38 +7,18 @@ set -euo pipefail # 2. Emits PDF_SKIPPED=1 and PDF_REASON=pandoc_missing # 3. No temp file leaks under controlled TMPDIR -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/render_report_pdf.sh" - -PASS=0 -FAIL=0 +TEST_NAME="render_pdf_smoke" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -fail() { - printf 'FAIL: %s\n' "$*" >&2 - FAIL=$((FAIL + 1)) -} - -pass() { - printf 'PASS: %s\n' "$*" - PASS=$((PASS + 1)) -} +SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/render_report_pdf.sh" # --------------------------------------------------------------------------- # Setup: temp dirs, PATH hiding, cleanup trap # --------------------------------------------------------------------------- ORIG_PATH="$PATH" -TMPROOT="" - -cleanup() { - PATH="$ORIG_PATH" - if [ -n "$TMPROOT" ] && [ -d "$TMPROOT" ]; then - rm -rf "$TMPROOT" - fi -} -trap cleanup EXIT INT TERM - -TMPROOT="$(mktemp -d)" +setup_tmproot FIXTURE_DIR="$TMPROOT/fixture" TEST_TMPDIR="$TMPROOT/tmpdir" mkdir -p "$FIXTURE_DIR" "$TEST_TMPDIR" @@ -64,7 +44,7 @@ if [ -f "$SCRIPT" ]; then pass "render_report_pdf.sh exists" else fail "render_report_pdf.sh not found at $SCRIPT" - printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" + print_results exit 1 fi @@ -78,22 +58,7 @@ fi # 2. Build filtered PATH that excludes pandoc # --------------------------------------------------------------------------- -FILTERED_PATH="" -IFS=':' -for segment in $ORIG_PATH; do - if [ -z "$segment" ]; then - continue - fi - if [ -x "$segment/pandoc" ]; then - continue - fi - if [ -n "$FILTERED_PATH" ]; then - FILTERED_PATH="$FILTERED_PATH:$segment" - else - FILTERED_PATH="$segment" - fi -done -unset IFS +build_filtered_path "pandoc" # Verify pandoc is hidden if ! PATH="$FILTERED_PATH" command -v pandoc >/dev/null 2>&1; then @@ -143,11 +108,7 @@ if [ -s "$STDERR_FILE" ]; then STDERR_CONTENT="$(cat "$STDERR_FILE")" fi -if echo "$STDERR_CONTENT" | grep -Eiq 'unbound variable|syntax error|segmentation fault|core dumped|panic'; then - fail "stderr contains crash diagnostic: $(echo "$STDERR_CONTENT" | grep -Ei 'unbound variable|syntax error|segmentation fault|core dumped|panic' | head -1)" -else - pass "no crash diagnostics in stderr" -fi +assert_no_crash_diagnostics "$STDERR_CONTENT" # --------------------------------------------------------------------------- # 5. Assert skip contract signals @@ -202,5 +163,4 @@ fi # Summary # --------------------------------------------------------------------------- -printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" -[ "$FAIL" -eq 0 ] +print_results diff --git a/tests/render_system_map_smoke_test.sh b/tests/render_system_map_smoke_test.sh index 5cdfd7ef0..aca20ea34 100644 --- a/tests/render_system_map_smoke_test.sh +++ b/tests/render_system_map_smoke_test.sh @@ -7,38 +7,18 @@ set -euo pipefail # 2. Emits SYSTEM_MAP_SKIPPED=1 and SYSTEM_MAP_REASON=graphviz_missing # 3. No temp file leaks under controlled TMPDIR -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/render_system_map.sh" - -PASS=0 -FAIL=0 +TEST_NAME="render_system_map_smoke" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -fail() { - printf 'FAIL: %s\n' "$*" >&2 - FAIL=$((FAIL + 1)) -} - -pass() { - printf 'PASS: %s\n' "$*" - PASS=$((PASS + 1)) -} +SCRIPT="$ROOT_DIR/vibe-code-audit/scripts/render_system_map.sh" # --------------------------------------------------------------------------- # Setup: temp dirs, PATH hiding, cleanup trap # --------------------------------------------------------------------------- ORIG_PATH="$PATH" -TMPROOT="" - -cleanup() { - PATH="$ORIG_PATH" - if [ -n "$TMPROOT" ] && [ -d "$TMPROOT" ]; then - rm -rf "$TMPROOT" - fi -} -trap cleanup EXIT INT TERM - -TMPROOT="$(mktemp -d)" +setup_tmproot FIXTURE_DIR="$TMPROOT/fixture" TEST_TMPDIR="$TMPROOT/tmpdir" mkdir -p "$FIXTURE_DIR" "$TEST_TMPDIR" @@ -64,7 +44,7 @@ if [ -f "$SCRIPT" ]; then pass "render_system_map.sh exists" else fail "render_system_map.sh not found at $SCRIPT" - printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" + print_results exit 1 fi @@ -78,22 +58,7 @@ fi # 2. Build filtered PATH that excludes dot # --------------------------------------------------------------------------- -FILTERED_PATH="" -IFS=':' -for segment in $ORIG_PATH; do - if [ -z "$segment" ]; then - continue - fi - if [ -x "$segment/dot" ]; then - continue - fi - if [ -n "$FILTERED_PATH" ]; then - FILTERED_PATH="$FILTERED_PATH:$segment" - else - FILTERED_PATH="$segment" - fi -done -unset IFS +build_filtered_path "dot" # Verify dot is hidden if ! PATH="$FILTERED_PATH" command -v dot >/dev/null 2>&1; then @@ -143,11 +108,7 @@ if [ -s "$STDERR_FILE" ]; then STDERR_CONTENT="$(cat "$STDERR_FILE")" fi -if echo "$STDERR_CONTENT" | grep -Eiq 'unbound variable|syntax error|segmentation fault|core dumped|panic'; then - fail "stderr contains crash diagnostic: $(echo "$STDERR_CONTENT" | grep -Ei 'unbound variable|syntax error|segmentation fault|core dumped|panic' | head -1)" -else - pass "no crash diagnostics in stderr" -fi +assert_no_crash_diagnostics "$STDERR_CONTENT" # --------------------------------------------------------------------------- # 5. Assert skip contract signals @@ -202,5 +163,4 @@ fi # Summary # --------------------------------------------------------------------------- -printf '\n--- Results: %d passed, %d failed ---\n' "$PASS" "$FAIL" -[ "$FAIL" -eq 0 ] +print_results diff --git a/tests/run_index_mock_smoke.sh b/tests/run_index_mock_smoke.sh index fafc881d8..1ee856b0a 100755 --- a/tests/run_index_mock_smoke.sh +++ b/tests/run_index_mock_smoke.sh @@ -679,11 +679,15 @@ EOF_CARGO rm -rf "$work_dir" ) -# Shellcheck gate for modified pipeline scripts +# Shellcheck gate for pipeline scripts and shared libraries PIPELINE_SCRIPTS=( + "$ROOT_DIR/vibe-code-audit/scripts/_lib.sh" "$ROOT_DIR/vibe-code-audit/scripts/run_index.sh" "$ROOT_DIR/vibe-code-audit/scripts/build_derived_artifacts.sh" "$ROOT_DIR/vibe-code-audit/scripts/build_read_plan.sh" + "$ROOT_DIR/vibe-code-audit/scripts/run_agentroot_embed.sh" + "$ROOT_DIR/vibe-code-audit/scripts/render_system_map.sh" + "$ROOT_DIR/vibe-code-audit/scripts/render_report_pdf.sh" ) if command -v shellcheck >/dev/null 2>&1; then sc_fail=0 diff --git a/tests/trap_cleanup_test.sh b/tests/trap_cleanup_test.sh index 348d91c71..c860edd65 100644 --- a/tests/trap_cleanup_test.sh +++ b/tests/trap_cleanup_test.sh @@ -1,12 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -PASS=0 -FAIL=0 -fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } -pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } +TEST_NAME="trap_cleanup" +# shellcheck source=_test_lib.sh +. "$(dirname "$0")/_test_lib.sh" -SCRIPTS_DIR="$(cd "$(dirname "$0")/../vibe-code-audit/scripts" && pwd)" +SCRIPTS_DIR="$ROOT_DIR/vibe-code-audit/scripts" echo "=== Trap & Cleanup Tests ===" @@ -233,6 +232,4 @@ fi # The trap declaration covers INT identically to TERM. # We verify INT registration via the static grep checks above. -echo "" -echo "=== Results: $PASS passed, $FAIL failed ===" -[ "$FAIL" -eq 0 ] || exit 1 +print_results diff --git a/vibe-code-audit/scripts/_lib.sh b/vibe-code-audit/scripts/_lib.sh index 801d94455..9d6b126a0 100644 --- a/vibe-code-audit/scripts/_lib.sh +++ b/vibe-code-audit/scripts/_lib.sh @@ -1,3 +1,4 @@ +# shellcheck shell=bash # _lib.sh — shared utility library for vibe-code-audit pipeline scripts # # Sourced by all pipeline scripts (run_index.sh, build_derived_artifacts.sh, @@ -25,10 +26,44 @@ die() { exit 1 } +# --------------------------------------------------------------------------- +# Config path constants +# --------------------------------------------------------------------------- + +# Canonical path for persistent embed configuration written by install.sh. +# Can be overridden via environment for testing. +EMBED_ENV_FILE="${EMBED_ENV_FILE:-$HOME/.config/vibe-code-audit/embed.env}" + # --------------------------------------------------------------------------- # File & pattern helpers # --------------------------------------------------------------------------- +# kv_from_file FILE KEY +# Extracts the value of KEY from a simple KEY=VALUE file. +# Reads the last occurrence of the key (last-occurrence-wins). +# Returns the value on stdout; empty string if not found. +kv_from_file() { + local file="${1-}" + local key="${2-}" + local value + value="$(sed -n "s/^${key}=//p" "$file" | tail -n1)" + printf '%s\n' "$value" +} + +# repo_has_file_named NAME +# Returns 0 (true) if a file named NAME exists anywhere in the current +# directory tree (respecting EXCLUDE_DIRS). Must be called from within +# the repository root (e.g., after pushd "$REPO_PATH_ABS"). +repo_has_file_named() { + local name="${1-}" + # shellcheck disable=SC2046 + if find . \( $(exclude_find_prune_args) \) -prune \ + -o -type f -name "$name" -print -quit | grep -q .; then + return 0 + fi + return 1 +} + # json_int_from_file FILE KEY # Extracts the first integer value for KEY from a JSON-like FILE. # Returns the integer on stdout; defaults to 0 if the file is missing, diff --git a/vibe-code-audit/scripts/run_agentroot_embed.sh b/vibe-code-audit/scripts/run_agentroot_embed.sh index 2b2cf13d5..a40840031 100755 --- a/vibe-code-audit/scripts/run_agentroot_embed.sh +++ b/vibe-code-audit/scripts/run_agentroot_embed.sh @@ -142,7 +142,7 @@ cleanup() { # Parse persistent embed config from installer if present (safe line-by-line, # never sourced as shell code to prevent command injection). -EMBED_ENV_FILE="$HOME/.config/vibe-code-audit/embed.env" +# EMBED_ENV_FILE is defined in _lib.sh; use that canonical constant. if [ -f "$EMBED_ENV_FILE" ]; then # Snapshot which keys are already set in the environment before parsing, # so pre-existing env vars take precedence but later lines in the file diff --git a/vibe-code-audit/scripts/run_index.sh b/vibe-code-audit/scripts/run_index.sh index 6f2fc5e5b..9b6b8037a 100755 --- a/vibe-code-audit/scripts/run_index.sh +++ b/vibe-code-audit/scripts/run_index.sh @@ -50,12 +50,7 @@ Environment: USAGE } -kv_from_file() { - file="$1" - key="$2" - value="$(sed -n "s/^${key}=//p" "$file" | tail -n1)" - printf '%s\n' "$value" -} +# kv_from_file() is now in _lib.sh REPO_PATH="" OUTPUT_DIR="" @@ -236,15 +231,7 @@ run_llmcc_graph() { pushd "$REPO_PATH_ABS" >/dev/null -repo_has_file_named() { - name="$1" - # shellcheck disable=SC2046 - if find . \( $(exclude_find_prune_args) \) -prune \ - -o -type f -name "$name" -print -quit | grep -q .; then - return 0 - fi - return 1 -} +# repo_has_file_named() is now in _lib.sh HAS_RUST=0 HAS_TS=0