1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-04 13:16:47 +00:00

refactor: improve brew cleanup timeout handling and remove app_caches, clean_extras, and optimize_core tests.

This commit is contained in:
Tw93
2026-01-03 12:53:31 +08:00
parent ac5c0e3c9d
commit 3cb21aad7e
26 changed files with 255 additions and 340 deletions

1
.gitignore vendored
View File

@@ -25,6 +25,7 @@ temp/
*.tmp *.tmp
*.temp *.temp
*.dmg *.dmg
tests/tmp-*
# Cache # Cache
.cache/ .cache/

View File

@@ -43,41 +43,32 @@ clean_homebrew() {
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Homebrew cleanup and autoremove..." MOLE_SPINNER_PREFIX=" " start_inline_spinner "Homebrew cleanup and autoremove..."
fi fi
fi fi
# Run cleanup/autoremove in parallel with a timeout guard. # Run cleanup/autoremove in parallel with timeout guard per command.
local timeout_seconds=${MO_BREW_TIMEOUT:-120} local timeout_seconds=${MO_BREW_TIMEOUT:-120}
local brew_tmp_file autoremove_tmp_file local brew_tmp_file autoremove_tmp_file
local brew_pid autoremove_pid local brew_pid autoremove_pid
local brew_exit=0
local autoremove_exit=0
if [[ "$skip_cleanup" == "false" ]]; then if [[ "$skip_cleanup" == "false" ]]; then
brew_tmp_file=$(create_temp_file) brew_tmp_file=$(create_temp_file)
(brew cleanup > "$brew_tmp_file" 2>&1) & run_with_timeout "$timeout_seconds" brew cleanup > "$brew_tmp_file" 2>&1 &
brew_pid=$! brew_pid=$!
fi fi
autoremove_tmp_file=$(create_temp_file) autoremove_tmp_file=$(create_temp_file)
(brew autoremove > "$autoremove_tmp_file" 2>&1) & run_with_timeout "$timeout_seconds" brew autoremove > "$autoremove_tmp_file" 2>&1 &
autoremove_pid=$! autoremove_pid=$!
local elapsed=0
local brew_done=false if [[ -n "$brew_pid" ]]; then
local autoremove_done=false wait "$brew_pid" 2> /dev/null || brew_exit=$?
[[ "$skip_cleanup" == "true" ]] && brew_done=true
while [[ "$brew_done" == "false" ]] || [[ "$autoremove_done" == "false" ]]; do
if [[ $elapsed -ge $timeout_seconds ]]; then
[[ -n "$brew_pid" ]] && kill -TERM $brew_pid 2> /dev/null || true
kill -TERM $autoremove_pid 2> /dev/null || true
break
fi fi
[[ -n "$brew_pid" ]] && { kill -0 $brew_pid 2> /dev/null || brew_done=true; } wait "$autoremove_pid" 2> /dev/null || autoremove_exit=$?
kill -0 $autoremove_pid 2> /dev/null || autoremove_done=true
sleep 1
((elapsed++))
done
local brew_success=false local brew_success=false
if [[ "$skip_cleanup" == "false" && -n "$brew_pid" ]]; then if [[ "$skip_cleanup" == "false" && $brew_exit -eq 0 ]]; then
if wait $brew_pid 2> /dev/null; then
brew_success=true brew_success=true
fi fi
fi
local autoremove_success=false local autoremove_success=false
if wait $autoremove_pid 2> /dev/null; then if [[ $autoremove_exit -eq 0 ]]; then
autoremove_success=true autoremove_success=true
fi fi
if [[ -t 1 ]]; then stop_inline_spinner; fi if [[ -t 1 ]]; then stop_inline_spinner; fi
@@ -100,7 +91,7 @@ clean_homebrew() {
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Homebrew cleanup (${removed_count} items)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Homebrew cleanup (${removed_count} items)"
fi fi
fi fi
elif [[ $elapsed -ge $timeout_seconds ]]; then elif [[ $brew_exit -eq 124 ]]; then
echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew cleanup timed out · run ${GRAY}brew cleanup${NC} manually" echo -e " ${YELLOW}${ICON_WARNING}${NC} Homebrew cleanup timed out · run ${GRAY}brew cleanup${NC} manually"
fi fi
# Process autoremove output - only show if packages were removed # Process autoremove output - only show if packages were removed
@@ -113,7 +104,7 @@ clean_homebrew() {
if [[ $removed_packages -gt 0 ]]; then if [[ $removed_packages -gt 0 ]]; then
echo -e " ${GREEN}${ICON_SUCCESS}${NC} Removed orphaned dependencies (${removed_packages} packages)" echo -e " ${GREEN}${ICON_SUCCESS}${NC} Removed orphaned dependencies (${removed_packages} packages)"
fi fi
elif [[ $elapsed -ge $timeout_seconds ]]; then elif [[ $autoremove_exit -eq 124 ]]; then
echo -e " ${YELLOW}${ICON_WARNING}${NC} Autoremove timed out · run ${GRAY}brew autoremove${NC} manually" echo -e " ${YELLOW}${ICON_WARNING}${NC} Autoremove timed out · run ${GRAY}brew autoremove${NC} manually"
fi fi
# Update cache timestamp on successful completion or when cleanup was intelligently skipped # Update cache timestamp on successful completion or when cleanup was intelligently skipped

View File

@@ -1,72 +0,0 @@
#!/usr/bin/env bats
setup_file() {
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
export PROJECT_ROOT
ORIGINAL_HOME="${HOME:-}"
export ORIGINAL_HOME
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-app-caches.XXXXXX")"
export HOME
mkdir -p "$HOME"
}
teardown_file() {
rm -rf "$HOME"
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
export HOME="$ORIGINAL_HOME"
fi
}
@test "clean_xcode_tools skips derived data when Xcode running" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" /bin/bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
pgrep() { return 0; }
safe_clean() { echo "$2"; }
clean_xcode_tools
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Xcode is running"* ]]
[[ "$output" != *"derived data"* ]]
[[ "$output" != *"archives"* ]]
}
@test "clean_media_players protects spotify offline cache" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" /bin/bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
mkdir -p "$HOME/Library/Application Support/Spotify/PersistentCache/Storage"
touch "$HOME/Library/Application Support/Spotify/PersistentCache/Storage/offline.bnk"
safe_clean() { echo "CLEAN:$2"; }
clean_media_players
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Spotify cache protected"* ]]
[[ "$output" != *"CLEAN: Spotify cache"* ]]
}
@test "clean_user_gui_applications calls all sections" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" /bin/bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
stop_section_spinner() { :; }
safe_clean() { :; }
clean_xcode_tools() { echo "xcode"; }
clean_code_editors() { echo "editors"; }
clean_communication_apps() { echo "comm"; }
clean_user_gui_applications
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"xcode"* ]]
[[ "$output" == *"editors"* ]]
[[ "$output" == *"comm"* ]]
}

View File

@@ -7,7 +7,7 @@ setup_file() {
ORIGINAL_HOME="${HOME:-}" ORIGINAL_HOME="${HOME:-}"
export ORIGINAL_HOME export ORIGINAL_HOME
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-app-caches-more.XXXXXX")" HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-app-caches.XXXXXX")"
export HOME export HOME
mkdir -p "$HOME" mkdir -p "$HOME"
@@ -20,6 +20,57 @@ teardown_file() {
fi fi
} }
@test "clean_xcode_tools skips derived data when Xcode running" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" /bin/bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
pgrep() { return 0; }
safe_clean() { echo "$2"; }
clean_xcode_tools
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Xcode is running"* ]]
[[ "$output" != *"derived data"* ]]
[[ "$output" != *"archives"* ]]
}
@test "clean_media_players protects spotify offline cache" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" /bin/bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
mkdir -p "$HOME/Library/Application Support/Spotify/PersistentCache/Storage"
touch "$HOME/Library/Application Support/Spotify/PersistentCache/Storage/offline.bnk"
safe_clean() { echo "CLEAN:$2"; }
clean_media_players
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Spotify cache protected"* ]]
[[ "$output" != *"CLEAN: Spotify cache"* ]]
}
@test "clean_user_gui_applications calls all sections" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" /bin/bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
stop_section_spinner() { :; }
safe_clean() { :; }
clean_xcode_tools() { echo "xcode"; }
clean_code_editors() { echo "editors"; }
clean_communication_apps() { echo "comm"; }
clean_user_gui_applications
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"xcode"* ]]
[[ "$output" == *"editors"* ]]
[[ "$output" == *"comm"* ]]
}
@test "clean_ai_apps calls expected caches" { @test "clean_ai_apps calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail set -euo pipefail

View File

@@ -74,8 +74,27 @@ EOF
skip "Homebrew not installed" skip "Homebrew not installed"
fi fi
run env HOME="$HOME" MO_BREW_TIMEOUT=5 "$PROJECT_ROOT/bin/clean.sh" --dry-run 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/brew.sh"
MO_BREW_TIMEOUT=5
CALL_LOG="$HOME/timeout.log"
run_with_timeout() {
echo "$1" >> "$CALL_LOG"
shift
"$@"
}
brew() { return 0; }
clean_homebrew
cat "$CALL_LOG"
EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[[ "$output" == *"5"* ]]
} }
@test "FINDER_METADATA_SENTINEL in whitelist protects .DS_Store files" { @test "FINDER_METADATA_SENTINEL in whitelist protects .DS_Store files" {

View File

@@ -1,102 +0,0 @@
#!/usr/bin/env bats
setup_file() {
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
export PROJECT_ROOT
ORIGINAL_HOME="${HOME:-}"
export ORIGINAL_HOME
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-clean-extras.XXXXXX")"
export HOME
mkdir -p "$HOME"
}
teardown_file() {
rm -rf "$HOME"
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
export HOME="$ORIGINAL_HOME"
fi
}
@test "clean_cloud_storage calls expected caches" {
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"
stop_section_spinner() { :; }
safe_clean() { echo "$2"; }
clean_cloud_storage
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Dropbox cache"* ]]
[[ "$output" == *"Google Drive cache"* ]]
}
@test "clean_virtualization_tools hits cache paths" {
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"
stop_section_spinner() { :; }
safe_clean() { echo "$2"; }
clean_virtualization_tools
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"VMware Fusion cache"* ]]
[[ "$output" == *"Parallels cache"* ]]
}
@test "clean_email_clients calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
safe_clean() { echo "$2"; }
clean_email_clients
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Spark cache"* ]]
[[ "$output" == *"Airmail cache"* ]]
}
@test "clean_note_apps calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
safe_clean() { echo "$2"; }
clean_note_apps
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Notion cache"* ]]
[[ "$output" == *"Obsidian cache"* ]]
}
@test "clean_task_apps calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
safe_clean() { echo "$2"; }
clean_task_apps
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Todoist cache"* ]]
[[ "$output" == *"Any.do cache"* ]]
}
@test "scan_external_volumes skips when no volumes" {
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"
run_with_timeout() { return 1; }
scan_external_volumes
EOF
[ "$status" -eq 0 ]
}

View File

@@ -7,7 +7,7 @@ setup_file() {
ORIGINAL_HOME="${HOME:-}" ORIGINAL_HOME="${HOME:-}"
export ORIGINAL_HOME export ORIGINAL_HOME
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-clean-extras-more.XXXXXX")" HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-clean-extras.XXXXXX")"
export HOME export HOME
mkdir -p "$HOME" mkdir -p "$HOME"
@@ -20,6 +20,87 @@ teardown_file() {
fi fi
} }
@test "clean_cloud_storage calls expected caches" {
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"
stop_section_spinner() { :; }
safe_clean() { echo "$2"; }
clean_cloud_storage
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Dropbox cache"* ]]
[[ "$output" == *"Google Drive cache"* ]]
}
@test "clean_virtualization_tools hits cache paths" {
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"
stop_section_spinner() { :; }
safe_clean() { echo "$2"; }
clean_virtualization_tools
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"VMware Fusion cache"* ]]
[[ "$output" == *"Parallels cache"* ]]
}
@test "clean_email_clients calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
safe_clean() { echo "$2"; }
clean_email_clients
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Spark cache"* ]]
[[ "$output" == *"Airmail cache"* ]]
}
@test "clean_note_apps calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
safe_clean() { echo "$2"; }
clean_note_apps
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Notion cache"* ]]
[[ "$output" == *"Obsidian cache"* ]]
}
@test "clean_task_apps calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/app_caches.sh"
safe_clean() { echo "$2"; }
clean_task_apps
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"Todoist cache"* ]]
[[ "$output" == *"Any.do cache"* ]]
}
@test "scan_external_volumes skips when no volumes" {
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"
run_with_timeout() { return 1; }
scan_external_volumes
EOF
[ "$status" -eq 0 ]
}
@test "clean_video_tools calls expected caches" { @test "clean_video_tools calls expected caches" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF' run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail set -euo pipefail

View File

@@ -234,16 +234,22 @@ source "$PROJECT_ROOT/lib/clean/brew.sh"
mkdir -p "$HOME/.cache/mole" mkdir -p "$HOME/.cache/mole"
rm -f "$HOME/.cache/mole/brew_last_cleanup" rm -f "$HOME/.cache/mole/brew_last_cleanup"
mkdir -p "$HOME/Library/Caches/Homebrew" start_inline_spinner(){ :; }
dd if=/dev/zero of="$HOME/Library/Caches/Homebrew/test.tar.gz" bs=1024 count=51200 2>/dev/null stop_inline_spinner(){ :; }
note_activity(){ :; }
run_with_timeout() {
local duration="$1"
shift
if [[ "$1" == "du" ]]; then
echo "51201 $3"
return 0
fi
"$@"
}
MO_BREW_TIMEOUT=2 MO_BREW_TIMEOUT=2
start_inline_spinner(){ :; } brew() {
stop_inline_spinner(){ :; }
note_activity(){ :; }
brew() {
case "$1" in case "$1" in
cleanup) cleanup)
echo "Removing: package" echo "Removing: package"
@@ -259,9 +265,7 @@ brew() {
esac esac
} }
clean_homebrew clean_homebrew
rm -rf "$HOME/Library/Caches/Homebrew"
EOF EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]

View File

@@ -20,6 +20,73 @@ teardown_file() {
fi fi
} }
@test "needs_permissions_repair returns true when home not writable" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" USER="tester" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
stat() { echo "root"; }
export -f stat
if needs_permissions_repair; then
echo "needs"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"needs"* ]]
}
@test "has_bluetooth_hid_connected detects HID" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
system_profiler() {
cat << 'OUT'
Bluetooth:
Apple Magic Mouse:
Connected: Yes
Type: Mouse
OUT
}
export -f system_profiler
if has_bluetooth_hid_connected; then
echo "hid"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"hid"* ]]
}
@test "is_ac_power detects AC power" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
pmset() { echo "AC Power"; }
export -f pmset
if is_ac_power; then
echo "ac"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"ac"* ]]
}
@test "is_memory_pressure_high detects warning" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
memory_pressure() { echo "warning"; }
export -f memory_pressure
if is_memory_pressure_high; then
echo "high"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"high"* ]]
}
@test "opt_system_maintenance reports DNS and Spotlight" { @test "opt_system_maintenance reports DNS and Spotlight" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF' run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF'
set -euo pipefail set -euo pipefail

View File

@@ -1,125 +0,0 @@
#!/usr/bin/env bats
setup_file() {
PROJECT_ROOT="$(cd "${BATS_TEST_DIRNAME}/.." && pwd)"
export PROJECT_ROOT
ORIGINAL_HOME="${HOME:-}"
export ORIGINAL_HOME
HOME="$(mktemp -d "${BATS_TEST_DIRNAME}/tmp-optimize-core.XXXXXX")"
export HOME
mkdir -p "$HOME"
}
teardown_file() {
rm -rf "$HOME"
if [[ -n "${ORIGINAL_HOME:-}" ]]; then
export HOME="$ORIGINAL_HOME"
fi
}
@test "needs_permissions_repair returns true when home not writable" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" USER="tester" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
stat() { echo "root"; }
export -f stat
if needs_permissions_repair; then
echo "needs"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"needs"* ]]
}
@test "has_bluetooth_hid_connected detects HID" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
system_profiler() {
cat << 'OUT'
Bluetooth:
Apple Magic Mouse:
Connected: Yes
Type: Mouse
OUT
}
export -f system_profiler
if has_bluetooth_hid_connected; then
echo "hid"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"hid"* ]]
}
@test "is_ac_power detects AC power" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
pmset() { echo "AC Power"; }
export -f pmset
if is_ac_power; then
echo "ac"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"ac"* ]]
}
@test "is_memory_pressure_high detects warning" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
memory_pressure() { echo "warning"; }
export -f memory_pressure
if is_memory_pressure_high; then
echo "high"
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"high"* ]]
}
@test "opt_launch_services_rebuild handles missing lsregister" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
opt_launch_services_rebuild
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"LaunchServices repaired"* ]]
}
@test "opt_msg uses dry-run output" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/core/common.sh"
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
opt_msg "dry"
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"dry"* ]]
}
@test "run_launchctl_unload skips in dry-run" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" MOLE_DRY_RUN=1 bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/optimize/tasks.sh"
launchctl() { echo "called"; }
export -f launchctl
run_launchctl_unload "/tmp/test.plist" false
EOF
[ "$status" -eq 0 ]
[[ "$output" != *"called"* ]]
}