diff --git a/.github/workflows/verify-split-files.yml b/.github/workflows/verify-split-files.yml index d5bb1eb..fea9b9f 100644 --- a/.github/workflows/verify-split-files.yml +++ b/.github/workflows/verify-split-files.yml @@ -33,6 +33,7 @@ on: - "tests/nodejs-key-refresh.sh" - "tests/forge-connection.sh" - "tests/failed-command-output.sh" + - "tests/stale-silent-record.sh" - "tests/exit-code-hints.sh" - "tests/forge-release-json.sh" - "tests/install-cleanup.sh" @@ -64,6 +65,7 @@ on: - "tests/nodejs-key-refresh.sh" - "tests/forge-connection.sh" - "tests/failed-command-output.sh" + - "tests/stale-silent-record.sh" - "tests/exit-code-hints.sh" - "tests/forge-release-json.sh" - "tests/install-cleanup.sh" @@ -124,6 +126,8 @@ jobs: - name: Failed command output tests run: bash tests/failed-command-output.sh + - name: Stale silent record tests + run: bash tests/stale-silent-record.sh - name: Exit code hint tests run: bash tests/exit-code-hints.sh diff --git a/core/error_handler.func b/core/error_handler.func index a79a367..c434404 100644 --- a/core/error_handler.func +++ b/core/error_handler.func @@ -665,12 +665,20 @@ error_handler() { command="${command//\$STD/}" # silent() knows the real failing command; the trap only sees the wrapper. + # Its record outlives a failure the caller handled, so it only counts for + # the line it came from; otherwise its capture would be sent for this one. local from_silent=0 if [[ -n "${_SILENT_FAILED_RC:-}" ]]; then - exit_code="$_SILENT_FAILED_RC" - command="$_SILENT_FAILED_CMD" - line_number="$_SILENT_FAILED_LINE" - from_silent=1 + if [[ "${_SILENT_FAILED_LINE:-}" == "$line_number" ]]; then + exit_code="$_SILENT_FAILED_RC" + command="$_SILENT_FAILED_CMD" + from_silent=1 + else + if [[ -n "${_SILENT_FAILED_LOG:-}" ]]; then + rm -f "${_SILENT_FAILED_LOG}.errinfo" 2>/dev/null + fi + unset _SILENT_FAILED_LOG + fi unset _SILENT_FAILED_RC _SILENT_FAILED_CMD _SILENT_FAILED_LINE fi diff --git a/tests/failed-command-output.sh b/tests/failed-command-output.sh index adbb722..163eefd 100644 --- a/tests/failed-command-output.sh +++ b/tests/failed-command-output.sh @@ -99,8 +99,9 @@ msg_error() { echo "error: $*"; } handler_output() { # error_handler exits, so it runs in a subshell with the state silent() leaves behind. ( - export _SILENT_FAILED_RC=1 _SILENT_FAILED_CMD="pnpm install" _SILENT_FAILED_LINE=34 _SILENT_FAILED_LOG="$LOGFILE" - error_handler 1 "silent pnpm install" 2>&1 + export _SILENT_FAILED_RC=1 _SILENT_FAILED_CMD="pnpm install" _SILENT_FAILED_LOG="$LOGFILE" + # The record only counts for the line it came from. + _SILENT_FAILED_LINE=$LINENO error_handler 1 "silent pnpm install" 2>&1 ) } reset diff --git a/tests/stale-silent-record.sh b/tests/stale-silent-record.sh new file mode 100644 index 0000000..fb6e5e8 --- /dev/null +++ b/tests/stale-silent-record.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# silent() leaves its record for the ERR trap. After `$STD cmd || true` the +# record and its .errinfo stayed until the next $STD call, so a later plain +# command's failure was reported as the handled one. +set -uo pipefail + +CORE_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +eval "$(awk '/^error_handler[(][)] [{]/,/^}/' "$CORE_DIR/core/error_handler.func")" + +TEST_DIR="$(mktemp -d)" +trap 'rm -rf -- "$TEST_DIR"' EXIT +LOG="$TEST_DIR/install.log" +ERRINFO="$LOG.errinfo" +passed=0 failed=0 + +_eh_explanation() { echo "explained"; } +_eh_report() { + printf 'REPORT %s|%s|%s\n' "$1" "$2" "$3" + exit 0 +} + +expect() { + if [[ "$2" == "$3" ]]; then + echo "PASS $1" + passed=$((passed + 1)) + else + echo "FAIL $1: expected <$2>, got <$3>" >&2 + failed=$((failed + 1)) + fi +} +handled_failure() { + echo "E: Unable to locate package intel-basekit" >"$LOG" + printf '%s\n' 'EXIT_CODE=100' 'LINE=5' 'COMMAND=apt install -y intel-basekit' '--- OUTPUT ---' 'E: Unable to locate package intel-basekit' >"$ERRINFO" + export _SILENT_FAILED_RC=100 _SILENT_FAILED_CMD='apt install -y intel-basekit' _SILENT_FAILED_LINE="$1" _SILENT_FAILED_LOG="$LOG" +} + +# A later failure on another line is its own. +handled_failure 5 +out="$(error_handler 1 'systemctl enable -q --now open-webui')" +expect "the later failure keeps its code and command" 1 "$(grep -c 'REPORT 1|systemctl enable -q --now open-webui|' <<<"$out")" +expect "the handled failure's capture is dropped" 0 "$(ls "$ERRINFO" 2>/dev/null | wc -l)" + +# On its own line the record is what the trap reports. +line=$LINENO; handled_failure "$line"; out="$(error_handler 1 'silent apt install -y intel-basekit')" +expect "silent's record is used for its own line" 1 "$(grep -c 'REPORT 100|apt install -y intel-basekit|' <<<"$out")" +expect "its capture is kept" 1 "$(ls "$ERRINFO" 2>/dev/null | wc -l)" + +echo "$passed passed, $failed failed" +((failed == 0))