1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 16:10:08 +00:00

Improve update checks and cleanup UX, add timeout regressions

This commit is contained in:
tw93
2026-03-05 12:00:07 +08:00
parent fbee8da9f7
commit 8e4b8a5e0d
21 changed files with 948 additions and 164 deletions

View File

@@ -122,3 +122,52 @@ EOF
[ "$status" -eq 0 ]
}
@test "batch_uninstall_applications tolerates brew autoremove timeout" {
local app_bundle="$HOME/Applications/BrewTimeout.app"
mkdir -p "$app_bundle"
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall/batch.sh"
request_sudo_access() { return 0; }
start_inline_spinner() { :; }
stop_inline_spinner() { :; }
get_file_owner() { whoami; }
get_path_size_kb() { echo "100"; }
bytes_to_human() { echo "$1"; }
drain_pending_input() { :; }
print_summary_block() { :; }
force_kill_app() { return 0; }
remove_apps_from_dock() { :; }
refresh_launch_services_after_uninstall() { echo "LS_REFRESH"; }
get_brew_cask_name() { echo "brew-timeout-cask"; return 0; }
brew_uninstall_cask() { return 0; }
run_with_timeout() {
local duration="$1"
shift
echo "TIMEOUT_CALL:$duration:$*" >> "$HOME/timeout_calls.log"
if [[ "$duration" == "30" ]]; then
return 124
fi
"$@"
}
selected_apps=("0|$HOME/Applications/BrewTimeout.app|BrewTimeout|com.example.brewtimeout|0|Never")
files_cleaned=0
total_items=0
total_size_cleaned=0
printf '\n' | batch_uninstall_applications
cat "$HOME/timeout_calls.log"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"TIMEOUT_CALL:30:bash -c HOMEBREW_NO_ENV_HINTS=1 brew autoremove 2>/dev/null"* ]]
[[ "$output" == *"LS_REFRESH"* ]]
}

View File

@@ -420,6 +420,80 @@ EOF
[[ "$output" == *"COUNT=0"* ]]
}
@test "check_homebrew_updates reports counts and exports update variables" {
run bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/check/all.sh"
run_with_timeout() {
local timeout="${1:-}"
shift
"$@"
}
brew() {
if [[ "$1" == "outdated" && "$2" == "--formula" && "$3" == "--quiet" ]]; then
printf "wget\njq\n"
return 0
fi
if [[ "$1" == "outdated" && "$2" == "--cask" && "$3" == "--quiet" ]]; then
printf "iterm2\n"
return 0
fi
return 0
}
check_homebrew_updates
echo "COUNTS=${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Homebrew"* ]]
[[ "$output" == *"2 formula, 1 cask available"* ]]
[[ "$output" == *"COUNTS=3:2:1"* ]]
}
@test "check_homebrew_updates shows timeout warning when brew query times out" {
run bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/check/all.sh"
run_with_timeout() { return 124; }
brew() { return 0; }
rm -f "$HOME/.cache/mole/brew_updates"
check_homebrew_updates
echo "COUNTS=${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Homebrew"* ]]
[[ "$output" == *"Check timed out"* ]]
[[ "$output" == *"COUNTS=0:0:0"* ]]
}
@test "check_homebrew_updates shows failure warning when brew query fails" {
run bash --noprofile --norc << 'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/check/all.sh"
run_with_timeout() { return 1; }
brew() { return 0; }
rm -f "$HOME/.cache/mole/brew_updates"
check_homebrew_updates
echo "COUNTS=${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Homebrew"* ]]
[[ "$output" == *"Check failed"* ]]
[[ "$output" == *"COUNTS=0:0:0"* ]]
}
@test "check_macos_update avoids slow softwareupdate scans" {
run bash --noprofile --norc << 'EOF'
set -euo pipefail

View File

@@ -38,6 +38,44 @@ EOF
[[ "$output" != *"Trash"* ]]
}
@test "clean_user_essentials falls back when Finder trash operations time out" {
mkdir -p "$HOME/.Trash"
touch "$HOME/.Trash/one.tmp" "$HOME/.Trash/two.tmp"
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/user.sh"
DRY_RUN=false
start_section_spinner() { :; }
stop_section_spinner() { :; }
safe_clean() { :; }
note_activity() { :; }
is_path_whitelisted() { return 1; }
debug_log() { :; }
run_with_timeout() {
local _duration="$1"
shift
if [[ "$1" == "osascript" ]]; then
return 124
fi
"$@"
}
safe_remove() {
local target="$1"
/bin/rm -rf "$target"
return 0
}
clean_user_essentials
[[ ! -e "$HOME/.Trash/one.tmp" ]] || exit 1
[[ ! -e "$HOME/.Trash/two.tmp" ]] || exit 1
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Trash · emptied, 2 items"* ]]
}
@test "clean_app_caches includes macOS system caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
@@ -58,6 +96,24 @@ EOF
[[ "$output" == *"Saved application states"* ]] || [[ "$output" == *"App caches"* ]]
}
@test "clean_app_caches shows spinner during initial app cache scan" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/user.sh"
start_section_spinner() { echo "SPIN_START:$1"; }
stop_section_spinner() { echo "SPIN_STOP"; }
safe_clean() { :; }
clean_support_app_data() { :; }
clean_group_container_caches() { :; }
clean_app_caches
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"SPIN_START:Scanning app caches..."* ]]
}
@test "clean_support_app_data targets crash, wallpaper, and messages preview caches only" {
local support_home="$HOME/support-cache-home-1"
run env HOME="$support_home" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'

View File

@@ -14,7 +14,7 @@ setup_file() {
}
teardown_file() {
rm -f "$PROJECT_ROOT/install_channel"
rm -rf "$HOME/.config/mole"
rm -rf "$HOME"
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
export HOME="$ORIGINAL_HOME"
@@ -46,9 +46,8 @@ SCRIPT
}
setup() {
rm -rf "$HOME/.config"
mkdir -p "$HOME"
rm -f "$PROJECT_ROOT/install_channel"
rm -rf "$HOME/.config/mole"
mkdir -p "$HOME/.config/mole"
}
@test "mole --help prints command overview" {
@@ -67,7 +66,8 @@ setup() {
@test "mole --version shows nightly channel metadata" {
expected_version="$(grep '^VERSION=' "$PROJECT_ROOT/mole" | head -1 | sed 's/VERSION=\"\(.*\)\"/\1/')"
cat > "$PROJECT_ROOT/install_channel" <<'EOF'
mkdir -p "$HOME/.config/mole"
cat > "$HOME/.config/mole/install_channel" <<'EOF'
CHANNEL=nightly
EOF
@@ -83,6 +83,12 @@ EOF
[[ "$output" == *"Unknown command: unknown-command"* ]]
}
@test "mole uninstall --whitelist returns unsupported option error" {
run env HOME="$HOME" "$PROJECT_ROOT/mole" uninstall --whitelist
[ "$status" -ne 0 ]
[[ "$output" == *"Unknown uninstall option: --whitelist"* ]]
}
@test "touchid status reports current configuration" {
run env HOME="$HOME" "$PROJECT_ROOT/mole" touchid status
[ "$status" -eq 0 ]

View File

@@ -299,3 +299,27 @@ EOF
[[ "$output" == *"$volumes_root/unused-runtime"* ]]
[[ "$output" != *"$volumes_root/in-use-runtime"* ]]
}
@test "clean_dev_mobile continues cleanup when simctl is unavailable" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/dev.sh"
check_android_ndk() { :; }
clean_xcode_documentation_cache() { :; }
clean_xcode_simulator_runtime_volumes() { :; }
clean_xcode_device_support() { echo "DEVICE_SUPPORT:$2"; }
safe_clean() { echo "SAFE_CLEAN:$2"; }
note_activity() { :; }
debug_log() { :; }
xcrun() { return 1; }
clean_dev_mobile
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"simctl not available"* ]]
[[ "$output" == *"DEVICE_SUPPORT:iOS DeviceSupport"* ]]
[[ "$output" == *"SAFE_CLEAN:Android SDK cache"* ]]
}

View File

@@ -56,7 +56,7 @@ setup() {
}
@test "Makefile has build target for Go binaries" {
run bash -c "grep -q 'go build' '$PROJECT_ROOT/Makefile'"
run bash -c "grep -Eq '(^|[[:space:]])(go|\\$\\(GO\\))[[:space:]]+build' '$PROJECT_ROOT/Makefile'"
[ "$status" -eq 0 ]
}

View File

@@ -313,6 +313,49 @@ EOF
[ "$status" -eq 0 ]
}
@test "refresh_launch_services_after_uninstall falls back after timeout" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/uninstall/batch.sh"
log_file="$HOME/lsregister-timeout.log"
: > "$log_file"
call_index=0
get_lsregister_path() { echo "/bin/echo"; }
debug_log() { echo "DEBUG:$*" >> "$log_file"; }
run_with_timeout() {
local duration="$1"
shift
call_index=$((call_index + 1))
echo "CALL${call_index}:$duration:$*" >> "$log_file"
if [[ "$call_index" -eq 2 ]]; then
return 124
fi
if [[ "$call_index" -eq 3 ]]; then
return 124
fi
return 0
}
if refresh_launch_services_after_uninstall; then
echo "RESULT:ok"
else
echo "RESULT:fail"
fi
cat "$log_file"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"RESULT:ok"* ]]
[[ "$output" == *"CALL2:15:/bin/echo -r -f -domain local -domain user -domain system"* ]]
[[ "$output" == *"CALL3:10:/bin/echo -r -f -domain local -domain user"* ]]
[[ "$output" == *"DEBUG:LaunchServices rebuild timed out, trying lighter version"* ]]
}
@test "remove_mole deletes manual binaries and caches" {
mkdir -p "$HOME/.local/bin"
touch "$HOME/.local/bin/mole"

View File

@@ -78,10 +78,32 @@ ask_for_updates
EOF
[ "$status" -eq 1 ] # ESC cancels
[[ "$output" == *"Homebrew, 3 formula, 2 cask"* ]]
[[ "$output" == *"App Store, 1 apps"* ]]
[[ "$output" == *"macOS system"* ]]
[[ "$output" == *"Mole"* ]]
[[ "$output" == *"Update Mole now?"* ]]
[[ "$output" == *"Run "* ]]
[[ "$output" == *"brew upgrade"* ]]
[[ "$output" == *"Software Update"* ]]
[[ "$output" == *"App Store"* ]]
[[ "$output" != *"AVAILABLE UPDATES"* ]]
}
@test "ask_for_updates with only macOS update shows settings hint without brew hint" {
run bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/manage/update.sh"
BREW_OUTDATED_COUNT=0
BREW_FORMULA_OUTDATED_COUNT=0
BREW_CASK_OUTDATED_COUNT=0
APPSTORE_UPDATE_COUNT=0
MACOS_UPDATE_AVAILABLE=true
MOLE_UPDATE_AVAILABLE=false
ask_for_updates
EOF
[ "$status" -eq 1 ]
[[ "$output" == *"Software Update"* ]]
[[ "$output" != *"brew upgrade"* ]]
[[ "$output" != *"AVAILABLE UPDATES"* ]]
}
@test "ask_for_updates accepts Enter when updates exist" {
@@ -97,10 +119,50 @@ ask_for_updates
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"AVAILABLE UPDATES"* ]]
[[ "$output" == *"Update Mole now?"* ]]
[[ "$output" == *"yes"* ]]
}
@test "ask_for_updates auto-detects brew updates when counts are unset" {
run bash --noprofile --norc <<'EOF'
set -euo pipefail
cat > "$MOCK_BIN_DIR/brew" <<'SCRIPT'
#!/usr/bin/env bash
if [[ "$1" == "outdated" && "$2" == "--formula" && "$3" == "--quiet" ]]; then
printf "wget\njq\n"
exit 0
fi
if [[ "$1" == "outdated" && "$2" == "--cask" && "$3" == "--quiet" ]]; then
printf "iterm2\n"
exit 0
fi
exit 0
SCRIPT
chmod +x "$MOCK_BIN_DIR/brew"
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/manage/update.sh"
unset BREW_OUTDATED_COUNT BREW_FORMULA_OUTDATED_COUNT BREW_CASK_OUTDATED_COUNT
APPSTORE_UPDATE_COUNT=0
MACOS_UPDATE_AVAILABLE=false
MOLE_UPDATE_AVAILABLE=false
set +e
ask_for_updates
ask_status=$?
set -e
echo "COUNTS:${BREW_OUTDATED_COUNT}:${BREW_FORMULA_OUTDATED_COUNT}:${BREW_CASK_OUTDATED_COUNT}"
exit "$ask_status"
EOF
[ "$status" -eq 1 ]
[[ "$output" == *"brew upgrade"* ]]
[[ "$output" == *"COUNTS:3:2:1"* ]]
[[ "$output" != *"AVAILABLE UPDATES"* ]]
}
@test "format_brew_update_label lists formula and cask counts" {
run bash --noprofile --norc <<'EOF'
set -euo pipefail