1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-06 11:22:57 +00:00

Fix macOS update detection timeout (#182)

Fixes #181

- Use softwareupdate -l --no-scan to avoid triggering fresh scans
- Increase timeout from 5s to 10s
- Prefer avoiding false negatives over false positives
- Add comprehensive test coverage for edge cases
This commit is contained in:
Aman Thanvi
2025-12-28 19:06:06 -05:00
committed by GitHub
parent 55a0aa5a0a
commit 9918e1aba6
2 changed files with 162 additions and 11 deletions

View File

@@ -236,7 +236,7 @@ check_macos_update() {
if [[ $(get_software_updates) == "Updates Available" ]]; then
updates_available="true"
# Verify with softwareupdate -l (short timeout) to reduce false positives
# Verify with softwareupdate using --no-scan (fast) to reduce false positives
local sw_output=""
local sw_status=0
local spinner_started=false
@@ -245,7 +245,9 @@ check_macos_update() {
spinner_started=true
fi
if ! sw_output=$(run_with_timeout 5 softwareupdate -l 2> /dev/null); then
if sw_output=$(run_with_timeout 10 softwareupdate -l --no-scan 2> /dev/null); then
:
else
sw_status=$?
fi
@@ -253,10 +255,9 @@ check_macos_update() {
stop_inline_spinner
fi
# If command failed, timed out, or returned empty, treat as no updates to avoid false positives
if [[ $sw_status -ne 0 || -z "$sw_output" ]]; then
updates_available="false"
elif echo "$sw_output" | grep -q "No new software available"; then
# 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
updates_available="false"
fi
fi

View File

@@ -186,25 +186,175 @@ EOF
[[ "$output" == *"COUNT=0"* ]]
}
@test "check_macos_update warns when update available" {
@test "check_macos_update avoids slow softwareupdate scans" {
run bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/check/all.sh"
softwareupdate() {
echo "* Label: macOS 99"
return 0
defaults() { echo "1"; }
run_with_timeout() {
local timeout="${1:-}"
shift
if [[ "$timeout" != "10" ]]; then
echo "BAD_TIMEOUT:$timeout"
return 124
fi
if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then
cat <<'OUT'
Software Update Tool
Software Update found the following new or updated software:
* Label: macOS 99
OUT
return 0
fi
return 124
}
start_inline_spinner(){ :; }
stop_inline_spinner(){ :; }
check_macos_update
echo "MACOS_UPDATE_AVAILABLE=$MACOS_UPDATE_AVAILABLE"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"macOS"* ]]
[[ "$output" == *"Update available"* ]]
[[ "$output" == *"MACOS_UPDATE_AVAILABLE=true"* ]]
[[ "$output" != *"BAD_TIMEOUT:"* ]]
}
@test "check_macos_update clears update flag when softwareupdate reports no updates" {
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"; }
run_with_timeout() {
local timeout="${1:-}"
shift
if [[ "$timeout" != "10" ]]; then
echo "BAD_TIMEOUT:$timeout"
return 124
fi
if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then
cat <<'OUT'
Software Update Tool
Finding available software
No new software available.
OUT
return 0
fi
return 124
}
start_inline_spinner(){ :; }
stop_inline_spinner(){ :; }
check_macos_update
echo "MACOS_UPDATE_AVAILABLE=$MACOS_UPDATE_AVAILABLE"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"System up to date"* ]]
[[ "$output" == *"MACOS_UPDATE_AVAILABLE=false"* ]]
[[ "$output" != *"BAD_TIMEOUT:"* ]]
}
@test "check_macos_update keeps update flag when softwareupdate times out" {
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"; }
run_with_timeout() {
local timeout="${1:-}"
shift
if [[ "$timeout" != "10" ]]; then
echo "BAD_TIMEOUT:$timeout"
return 124
fi
if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then
return 124
fi
return 124
}
start_inline_spinner(){ :; }
stop_inline_spinner(){ :; }
check_macos_update
echo "MACOS_UPDATE_AVAILABLE=$MACOS_UPDATE_AVAILABLE"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Update available"* ]]
[[ "$output" == *"MACOS_UPDATE_AVAILABLE=true"* ]]
[[ "$output" != *"BAD_TIMEOUT:"* ]]
}
@test "check_macos_update keeps update flag when softwareupdate returns empty output" {
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"; }
run_with_timeout() {
local timeout="${1:-}"
shift
if [[ "$timeout" != "10" ]]; then
echo "BAD_TIMEOUT:$timeout"
return 124
fi
if [[ "${1:-}" == "softwareupdate" && "${2:-}" == "-l" && "${3:-}" == "--no-scan" ]]; then
return 0
fi
return 124
}
start_inline_spinner(){ :; }
stop_inline_spinner(){ :; }
check_macos_update
echo "MACOS_UPDATE_AVAILABLE=$MACOS_UPDATE_AVAILABLE"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Update available"* ]]
[[ "$output" == *"MACOS_UPDATE_AVAILABLE=true"* ]]
[[ "$output" != *"BAD_TIMEOUT:"* ]]
}
@test "check_macos_update skips softwareupdate when defaults shows no updates" {
run bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/check/all.sh"
defaults() { echo "0"; }
run_with_timeout() {
echo "SHOULD_NOT_CALL_SOFTWAREUPDATE"
return 0
}
check_macos_update
echo "MACOS_UPDATE_AVAILABLE=$MACOS_UPDATE_AVAILABLE"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"System up to date"* ]]
[[ "$output" == *"MACOS_UPDATE_AVAILABLE=false"* ]]
[[ "$output" != *"SHOULD_NOT_CALL_SOFTWAREUPDATE"* ]]
}
@test "run_with_timeout succeeds without GNU timeout" {