From 5f7df6235ec37739bd8985c4c70811d7ec15c3bc Mon Sep 17 00:00:00 2001 From: Tw93 Date: Mon, 29 Dec 2025 08:20:27 +0800 Subject: [PATCH] enhance macOS update check with configurable timeout and debug logging --- lib/check/all.sh | 15 ++++++-- tests/system_maintenance.bats | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/lib/check/all.sh b/lib/check/all.sh index 5062db6..e4e4781 100644 --- a/lib/check/all.sh +++ b/lib/check/all.sh @@ -236,7 +236,10 @@ check_macos_update() { if [[ $(get_software_updates) == "Updates Available" ]]; then updates_available="true" - # Verify with softwareupdate using --no-scan (fast) to reduce false positives + # Verify with softwareupdate using --no-scan to avoid triggering a fresh scan + # which can timeout. We prioritize avoiding false negatives (missing actual updates) + # over false positives, so we only clear the update flag when softwareupdate + # explicitly reports "No new software available" local sw_output="" local sw_status=0 local spinner_started=false @@ -245,7 +248,8 @@ check_macos_update() { spinner_started=true fi - if sw_output=$(run_with_timeout 10 softwareupdate -l --no-scan 2> /dev/null); then + local softwareupdate_timeout="${MO_SOFTWAREUPDATE_TIMEOUT:-10}" + if sw_output=$(run_with_timeout "$softwareupdate_timeout" softwareupdate -l --no-scan 2> /dev/null); then : else sw_status=$? @@ -255,9 +259,14 @@ check_macos_update() { stop_inline_spinner fi + # Debug logging for troubleshooting + if [[ -n "${MO_DEBUG:-}" ]]; then + echo "[DEBUG] softwareupdate exit status: $sw_status, output lines: $(echo "$sw_output" | wc -l | tr -d ' ')" >&2 + fi + # Prefer avoiding false negatives: if the system indicates updates are pending, # only clear the flag when softwareupdate explicitly reports no updates. - if [[ $sw_status -eq 0 && -n "$sw_output" ]] && echo "$sw_output" | grep -qE '^[[:space:]]*No new software available'; then + if [[ $sw_status -eq 0 && -n "$sw_output" ]] && echo "$sw_output" | grep -qE '^\s*No new software available\s*\.?\s*$'; then updates_available="false" fi fi diff --git a/tests/system_maintenance.bats b/tests/system_maintenance.bats index 449484a..16e3eb7 100644 --- a/tests/system_maintenance.bats +++ b/tests/system_maintenance.bats @@ -357,6 +357,72 @@ EOF [[ "$output" != *"SHOULD_NOT_CALL_SOFTWAREUPDATE"* ]] } +@test "check_macos_update respects MO_SOFTWAREUPDATE_TIMEOUT" { + run bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/check/all.sh" + +defaults() { echo "1"; } + +export MO_SOFTWAREUPDATE_TIMEOUT=15 + +run_with_timeout() { + local timeout="${1:-}" + shift + if [[ "$timeout" != "15" ]]; then + echo "BAD_TIMEOUT:$timeout" + return 124 + fi + if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then + echo "No new software available." + return 0 + fi + return 124 +} + +start_inline_spinner(){ :; } +stop_inline_spinner(){ :; } + +check_macos_update +echo "TEST_PASSED" +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"TEST_PASSED"* ]] + [[ "$output" != *"BAD_TIMEOUT:"* ]] +} + +@test "check_macos_update outputs debug info when MO_DEBUG set" { + run bash --noprofile --norc <<'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" +source "$PROJECT_ROOT/lib/check/all.sh" + +defaults() { echo "1"; } + +export MO_DEBUG=1 + +run_with_timeout() { + local timeout="${1:-}" + shift + if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then + echo "No new software available." + return 0 + fi + return 124 +} + +start_inline_spinner(){ :; } +stop_inline_spinner(){ :; } + +check_macos_update 2>&1 +EOF + + [ "$status" -eq 0 ] + [[ "$output" == *"[DEBUG] softwareupdate exit status:"* ]] +} + @test "run_with_timeout succeeds without GNU timeout" { run bash --noprofile --norc -c ' set -euo pipefail