mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 11:31:46 +00:00
feat: remove SQLite vacuum optimization, enhance CJK/emoji width calculation, and improve system cleanup and UI feedback.
This commit is contained in:
@@ -255,7 +255,7 @@ Mole uses **BATS (Bash Automated Testing System)** for automated testing.
|
||||
|---------------|----------|-----------|
|
||||
| Core File Operations | 95% | Path validation, symlink detection, permissions |
|
||||
| Cleaning Logic | 87% | Orphan detection, 60-day rule, vendor whitelist |
|
||||
| Optimization | 82% | SQLite VACUUM, cache cleanup, timeouts |
|
||||
| Optimization | 82% | Cache cleanup, timeouts |
|
||||
| System Maintenance | 90% | Time Machine, network volumes, crash recovery |
|
||||
| Security Controls | 100% | Path traversal, command injection, symlinks |
|
||||
|
||||
@@ -315,7 +315,6 @@ Mole relies on standard macOS system binaries (all SIP-protected):
|
||||
| `tmutil` | Time Machine interaction | Skip TM cleanup |
|
||||
| `dscacheutil` | System cache rebuilding | Optional optimization |
|
||||
| `diskutil` | Volume information | Skip network volumes |
|
||||
| `sqlite3` | Database optimization | Skip SQLite VACUUM |
|
||||
|
||||
### Go Dependencies (Interactive Tools)
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ files_cleaned=0
|
||||
total_size_cleaned=0
|
||||
whitelist_skipped_count=0
|
||||
|
||||
# shellcheck disable=SC2329
|
||||
note_activity() {
|
||||
if [[ $TRACK_SECTION -eq 1 ]]; then
|
||||
SECTION_ACTIVITY=1
|
||||
@@ -113,6 +114,7 @@ note_activity() {
|
||||
|
||||
# Cleanup background processes
|
||||
CLEANUP_DONE=false
|
||||
# shellcheck disable=SC2329
|
||||
cleanup() {
|
||||
local signal="${1:-EXIT}"
|
||||
local exit_code="${2:-$?}"
|
||||
@@ -124,7 +126,7 @@ cleanup() {
|
||||
CLEANUP_DONE=true
|
||||
|
||||
# Stop all spinners and clear the line
|
||||
if [[ -n "$INLINE_SPINNER_PID" ]]; then
|
||||
if [[ -n "${INLINE_SPINNER_PID:-}" ]] && kill -0 "$INLINE_SPINNER_PID" 2> /dev/null; then
|
||||
kill "$INLINE_SPINNER_PID" 2> /dev/null || true
|
||||
wait "$INLINE_SPINNER_PID" 2> /dev/null || true
|
||||
INLINE_SPINNER_PID=""
|
||||
@@ -176,6 +178,7 @@ end_section() {
|
||||
TRACK_SECTION=0
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2329
|
||||
safe_clean() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
return 0
|
||||
@@ -767,6 +770,7 @@ main() {
|
||||
hide_cursor
|
||||
perform_cleanup
|
||||
show_cursor
|
||||
exit 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -142,14 +142,24 @@ func coloredProgressBar(value, max int64, percent float64) string {
|
||||
return bar + colorReset
|
||||
}
|
||||
|
||||
// Calculate display width considering CJK characters.
|
||||
// Calculate display width considering CJK characters and Emoji.
|
||||
func runeWidth(r rune) int {
|
||||
if r >= 0x4E00 && r <= 0x9FFF ||
|
||||
r >= 0x3400 && r <= 0x4DBF ||
|
||||
r >= 0x3040 && r <= 0x30FF ||
|
||||
r >= 0x31F0 && r <= 0x31FF ||
|
||||
r >= 0xAC00 && r <= 0xD7AF ||
|
||||
r >= 0xFF00 && r <= 0xFFEF {
|
||||
if r >= 0x4E00 && r <= 0x9FFF || // CJK Unified Ideographs
|
||||
r >= 0x3400 && r <= 0x4DBF || // CJK Extension A
|
||||
r >= 0x20000 && r <= 0x2A6DF || // CJK Extension B
|
||||
r >= 0x2A700 && r <= 0x2B73F || // CJK Extension C
|
||||
r >= 0x2B740 && r <= 0x2B81F || // CJK Extension D
|
||||
r >= 0x2B820 && r <= 0x2CEAF || // CJK Extension E
|
||||
r >= 0x3040 && r <= 0x30FF || // Hiragana and Katakana
|
||||
r >= 0x31F0 && r <= 0x31FF || // Katakana Phonetic Extensions
|
||||
r >= 0xAC00 && r <= 0xD7AF || // Hangul Syllables
|
||||
r >= 0xFF00 && r <= 0xFFEF || // Fullwidth Forms
|
||||
r >= 0x1F300 && r <= 0x1F6FF || // Miscellaneous Symbols and Pictographs (includes Transport)
|
||||
r >= 0x1F900 && r <= 0x1F9FF || // Supplemental Symbols and Pictographs
|
||||
r >= 0x2600 && r <= 0x26FF || // Miscellaneous Symbols
|
||||
r >= 0x2700 && r <= 0x27BF || // Dingbats
|
||||
r >= 0xFE10 && r <= 0xFE1F || // Vertical Forms
|
||||
r >= 0x1F000 && r <= 0x1F02F { // Mahjong Tiles
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
|
||||
@@ -128,8 +128,6 @@ EOF
|
||||
items+=('maintenance_scripts|System Log Rotation|Rotate and compress system logs with newsyslog|true')
|
||||
items+=('swap_cleanup|Virtual Memory Refresh|Reset swap files and dynamic pager service|true')
|
||||
items+=('network_optimization|Network Stack Optimization|Refresh DNS, rebuild ARP & restart mDNSResponder|true')
|
||||
items+=('sqlite_vacuum|SQLite Database Optimization|Optimize user databases with VACUUM to reduce size|true')
|
||||
|
||||
# Output items as JSON
|
||||
local first=true
|
||||
for item in "${items[@]}"; do
|
||||
|
||||
@@ -155,29 +155,6 @@ clean_deep_system() {
|
||||
fi
|
||||
fi
|
||||
debug_log "Core symbolication cache section completed"
|
||||
|
||||
# Clean Aliyun/DingTalk security component logs (if exists, can be 2-3GB)
|
||||
# Only clean if the software is installed
|
||||
debug_log "Checking Aliyun security components..."
|
||||
local ali_cleaned=0
|
||||
|
||||
# List of Aliyun-related paths to clean
|
||||
local -a ali_paths=(
|
||||
"/private/var/root/Library/Application Support/ali_bas/bas_http_info/ali_bas_httpclient"
|
||||
"/private/var/root/Library/Application Support/Aliedr/logs_dir"
|
||||
"/private/var/root/Library/Application Support/Aliedr/cache"
|
||||
)
|
||||
|
||||
# Clean each path if exists
|
||||
for ali_path in "${ali_paths[@]}"; do
|
||||
if sudo test -e "$ali_path" 2> /dev/null; then
|
||||
debug_log "Found Aliyun component: $ali_path, removing..."
|
||||
safe_sudo_remove "$ali_path" && ((ali_cleaned++)) || true
|
||||
fi
|
||||
done
|
||||
|
||||
debug_log "Aliyun security components check completed, cleaned: $ali_cleaned"
|
||||
[[ $ali_cleaned -gt 0 ]] && log_success "Aliyun security component data"
|
||||
}
|
||||
|
||||
# Clean incomplete Time Machine backups
|
||||
|
||||
@@ -520,6 +520,11 @@ should_protect_path() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Protect Notes cache (search index issues)
|
||||
if [[ "$path_lower" =~ com\.apple\.notes ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 2. Protect caches critical for system UI rendering
|
||||
# These caches are essential for modern macOS (Sonoma/Sequoia) system UI rendering
|
||||
case "$path" in
|
||||
|
||||
@@ -429,11 +429,18 @@ bytes_to_human_kb() {
|
||||
get_brand_name() {
|
||||
local name="$1"
|
||||
|
||||
# Detect if system primary language is Chinese
|
||||
local is_chinese=false
|
||||
local sys_lang
|
||||
sys_lang=$(defaults read -g AppleLanguages 2> /dev/null | grep -o 'zh-Hans\|zh-Hant\|zh' | head -1 || echo "")
|
||||
[[ -n "$sys_lang" ]] && is_chinese=true
|
||||
# Detect if system primary language is Chinese (Cached)
|
||||
if [[ -z "${MOLE_IS_CHINESE_SYSTEM:-}" ]]; then
|
||||
local sys_lang
|
||||
sys_lang=$(defaults read -g AppleLanguages 2> /dev/null | grep -o 'zh-Hans\|zh-Hant\|zh' | head -1 || echo "")
|
||||
if [[ -n "$sys_lang" ]]; then
|
||||
export MOLE_IS_CHINESE_SYSTEM="true"
|
||||
else
|
||||
export MOLE_IS_CHINESE_SYSTEM="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
local is_chinese="${MOLE_IS_CHINESE_SYSTEM}"
|
||||
|
||||
# Return localized names based on system language
|
||||
if [[ "$is_chinese" == true ]]; then
|
||||
|
||||
@@ -284,7 +284,7 @@ stop_inline_spinner() {
|
||||
# Try graceful TERM first, then force KILL if needed
|
||||
if kill -0 "$INLINE_SPINNER_PID" 2> /dev/null; then
|
||||
kill -TERM "$INLINE_SPINNER_PID" 2> /dev/null || true
|
||||
sleep 0.05 2> /dev/null || true
|
||||
sleep 0.1 2> /dev/null || true
|
||||
# Force kill if still running
|
||||
if kill -0 "$INLINE_SPINNER_PID" 2> /dev/null; then
|
||||
kill -KILL "$INLINE_SPINNER_PID" 2> /dev/null || true
|
||||
|
||||
@@ -69,7 +69,20 @@ opt_cache_refresh() {
|
||||
|
||||
# Run periodic maintenance scripts
|
||||
opt_maintenance_scripts() {
|
||||
if [[ -t 1 ]]; then
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Rotating logs..."
|
||||
fi
|
||||
|
||||
local success=false
|
||||
if run_with_timeout 120 sudo newsyslog > /dev/null 2>&1; then
|
||||
success=true
|
||||
fi
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if [[ "$success" == "true" ]]; then
|
||||
echo -e " ${GREEN}✓${NC} System logs rotated"
|
||||
else
|
||||
echo -e " ${YELLOW}!${NC} Failed to rotate logs"
|
||||
@@ -204,96 +217,6 @@ opt_network_optimization() {
|
||||
echo -e " ${GREEN}✓${NC} mDNSResponder optimized"
|
||||
}
|
||||
|
||||
# SQLite database optimization
|
||||
# Runs VACUUM on safe user-level databases to reduce size and improve performance
|
||||
# Only operates on databases that are not in use and meet size threshold
|
||||
opt_sqlite_vacuum() {
|
||||
local optimized_count=0
|
||||
local total_saved_kb=0
|
||||
local min_size_kb=1024 # Only optimize databases larger than 1MB
|
||||
|
||||
# List of safe databases to optimize
|
||||
# Exclude critical system databases like Mail, Messages, Photos
|
||||
local -a safe_databases=(
|
||||
"$HOME/Library/Safari/History.db"
|
||||
"$HOME/Library/Safari/CloudTabs.db"
|
||||
)
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
MOLE_SPINNER_PREFIX=" " start_inline_spinner "Scanning databases..."
|
||||
fi
|
||||
|
||||
for db_path in "${safe_databases[@]}"; do
|
||||
# Check if database exists
|
||||
[[ ! -f "$db_path" ]] && continue
|
||||
|
||||
# Check if it's a SQLite database
|
||||
if ! file "$db_path" 2> /dev/null | grep -q "SQLite"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check size threshold
|
||||
local db_size_kb
|
||||
db_size_kb=$(get_path_size_kb "$db_path")
|
||||
[[ $db_size_kb -lt $min_size_kb ]] && continue
|
||||
|
||||
# Check if database is in use
|
||||
if lsof "$db_path" > /dev/null 2>&1; then
|
||||
debug_log "Skipping $db_path - in use"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check disk space (VACUUM needs ~2x database size as temporary space)
|
||||
local free_space_kb
|
||||
free_space_kb=$(df -k "$(dirname "$db_path")" | tail -1 | awk '{print $4}')
|
||||
if [[ $free_space_kb -lt $((db_size_kb * 2)) ]]; then
|
||||
debug_log "Skipping $db_path - insufficient disk space"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Verify database integrity before VACUUM
|
||||
if ! sqlite3 "$db_path" "PRAGMA integrity_check;" 2> /dev/null | grep -q "ok"; then
|
||||
debug_log "Skipping $db_path - integrity check failed"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get size before optimization
|
||||
local size_before=$db_size_kb
|
||||
|
||||
# Perform VACUUM with error handling
|
||||
if sqlite3 "$db_path" "VACUUM;" 2> /dev/null; then
|
||||
# Verify database integrity after VACUUM
|
||||
if ! sqlite3 "$db_path" "PRAGMA integrity_check;" 2> /dev/null | grep -q "ok"; then
|
||||
debug_log "Warning: $db_path integrity check failed after VACUUM"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get size after optimization
|
||||
local size_after
|
||||
size_after=$(get_path_size_kb "$db_path")
|
||||
local saved_kb=$((size_before - size_after))
|
||||
|
||||
if [[ $saved_kb -gt 0 ]]; then
|
||||
((optimized_count++))
|
||||
((total_saved_kb += saved_kb))
|
||||
fi
|
||||
else
|
||||
debug_log "Failed to VACUUM $db_path"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -t 1 ]]; then
|
||||
stop_inline_spinner
|
||||
fi
|
||||
|
||||
if [[ $optimized_count -gt 0 ]]; then
|
||||
local saved_human=$(bytes_to_human "$((total_saved_kb * 1024))")
|
||||
echo -e " ${GREEN}✓${NC} Optimized $optimized_count databases ($saved_human saved)"
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Databases already optimized"
|
||||
fi
|
||||
}
|
||||
|
||||
# Clean Spotlight user caches
|
||||
|
||||
# Execute optimization by action name
|
||||
@@ -312,7 +235,6 @@ execute_optimization() {
|
||||
local_snapshots) opt_local_snapshots ;;
|
||||
fix_broken_configs) opt_fix_broken_configs ;;
|
||||
network_optimization) opt_network_optimization ;;
|
||||
sqlite_vacuum) opt_sqlite_vacuum ;;
|
||||
*)
|
||||
echo -e "${YELLOW}${ICON_ERROR}${NC} Unknown action: $action"
|
||||
return 1
|
||||
|
||||
2
mole
2
mole
@@ -25,7 +25,7 @@ source "$SCRIPT_DIR/lib/core/common.sh"
|
||||
trap cleanup_temp_files EXIT INT TERM
|
||||
|
||||
# Version info
|
||||
VERSION="1.15.4"
|
||||
VERSION="1.15.5"
|
||||
MOLE_TAGLINE="Deep clean and optimize your Mac."
|
||||
|
||||
# Check TouchID configuration
|
||||
|
||||
@@ -38,9 +38,9 @@ if command -v shellcheck > /dev/null 2>&1; then
|
||||
SHELL_FILES=$(find . -type f \( -name "*.sh" -o -name "mole" \) -not -path "./tests/*" -not -path "./.git/*")
|
||||
FILE_COUNT=$(echo "$SHELL_FILES" | wc -l | tr -d ' ')
|
||||
|
||||
if shellcheck mole bin/*.sh lib/*.sh scripts/*.sh 2>&1 | grep -q "SC[0-9]"; then
|
||||
if shellcheck mole bin/*.sh lib/*/*.sh scripts/*.sh 2>&1 | grep -q "SC[0-9]"; then
|
||||
echo -e "${YELLOW}⚠ ShellCheck found some issues (non-critical):${NC}"
|
||||
shellcheck mole bin/*.sh lib/*.sh scripts/*.sh 2>&1 | head -20
|
||||
shellcheck mole bin/*.sh lib/*/*.sh scripts/*.sh 2>&1 | head -20
|
||||
echo -e "${GREEN}✓ ShellCheck completed (${FILE_COUNT} files checked)${NC}\n"
|
||||
else
|
||||
echo -e "${GREEN}✓ ShellCheck passed (${FILE_COUNT} files checked)${NC}\n"
|
||||
|
||||
@@ -469,275 +469,3 @@ EOF
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"WOULD_CLEAN=no"* ]]
|
||||
}
|
||||
|
||||
@test "clean_deep_system cleans Aliyun components when present" {
|
||||
run bash --noprofile --norc <<'EOF'
|
||||
set -euo pipefail
|
||||
CLEANED_PATHS=""
|
||||
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||
source "$PROJECT_ROOT/lib/clean/system.sh"
|
||||
|
||||
sudo() {
|
||||
if [[ "$1" == "test" ]]; then
|
||||
# Simulate Aliyun paths exist
|
||||
[[ "$3" == *"ali_bas"* || "$3" == *"Aliedr"* ]] && return 0
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
safe_sudo_remove() {
|
||||
CLEANED_PATHS="$CLEANED_PATHS|$1"
|
||||
return 0
|
||||
}
|
||||
safe_sudo_find_delete() { return 0; }
|
||||
log_success() { :; }
|
||||
start_section_spinner() { :; }
|
||||
stop_section_spinner() { :; }
|
||||
is_sip_enabled() { return 1; }
|
||||
find() { return 0; }
|
||||
run_with_timeout() { shift; "$@"; }
|
||||
|
||||
clean_deep_system
|
||||
echo "$CLEANED_PATHS"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"ali_bas"* ]]
|
||||
[[ "$output" == *"Aliedr"* ]]
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Tests for SQLite VACUUM optimization (v1.15.2)
|
||||
# ============================================================================
|
||||
|
||||
skip_if_no_sqlite3() {
|
||||
command -v sqlite3 > /dev/null 2>&1 || skip "sqlite3 not available"
|
||||
}
|
||||
|
||||
@test "opt_sqlite_vacuum optimizes Safari databases" {
|
||||
skip_if_no_sqlite3
|
||||
# Create test databases
|
||||
mkdir -p "$HOME/Library/Safari"
|
||||
|
||||
# Clean up any existing databases from previous tests
|
||||
rm -f "$HOME/Library/Safari/History.db" "$HOME/Library/Safari/CloudTabs.db"
|
||||
|
||||
# Create a simple SQLite database
|
||||
run sqlite3 "$HOME/Library/Safari/History.db" "CREATE TABLE test(id INTEGER); INSERT INTO test VALUES(1);"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
# Create another test database
|
||||
run sqlite3 "$HOME/Library/Safari/CloudTabs.db" "CREATE TABLE test(id INTEGER); INSERT INTO test VALUES(1);"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
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/optimize/tasks.sh"
|
||||
|
||||
# Mock functions to avoid actual operations
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
lsof() { return 1; } # Database not in use
|
||||
|
||||
opt_sqlite_vacuum
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Databases already optimized"* || "$output" == *"Optimized"* ]]
|
||||
}
|
||||
|
||||
@test "opt_sqlite_vacuum checks database integrity before VACUUM" {
|
||||
skip_if_no_sqlite3
|
||||
mkdir -p "$HOME/Library/Safari"
|
||||
|
||||
# Clean up and create database
|
||||
rm -f "$HOME/Library/Safari/History.db"
|
||||
sqlite3 "$HOME/Library/Safari/History.db" "CREATE TABLE test(id INTEGER);"
|
||||
|
||||
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/optimize/tasks.sh"
|
||||
|
||||
INTEGRITY_CHECKS=0
|
||||
original_sqlite3=$(command -v sqlite3)
|
||||
|
||||
sqlite3() {
|
||||
if [[ "$2" == *"integrity_check"* ]]; then
|
||||
((INTEGRITY_CHECKS++))
|
||||
echo "ok"
|
||||
return 0
|
||||
fi
|
||||
"$original_sqlite3" "$@"
|
||||
}
|
||||
export -f sqlite3
|
||||
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
lsof() { return 1; }
|
||||
|
||||
opt_sqlite_vacuum
|
||||
echo "INTEGRITY_CHECKS=$INTEGRITY_CHECKS"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
# Should check integrity at least once (before VACUUM)
|
||||
[[ "$output" == *"INTEGRITY_CHECKS="* ]]
|
||||
}
|
||||
|
||||
@test "opt_sqlite_vacuum skips databases in use" {
|
||||
skip_if_no_sqlite3
|
||||
mkdir -p "$HOME/Library/Safari"
|
||||
rm -f "$HOME/Library/Safari/History.db"
|
||||
sqlite3 "$HOME/Library/Safari/History.db" "CREATE TABLE test(id INTEGER);"
|
||||
|
||||
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/optimize/tasks.sh"
|
||||
|
||||
VACUUM_COUNT=0
|
||||
original_sqlite3=$(command -v sqlite3)
|
||||
|
||||
sqlite3() {
|
||||
if [[ "$2" == "VACUUM;" ]]; then
|
||||
((VACUUM_COUNT++))
|
||||
fi
|
||||
"$original_sqlite3" "$@"
|
||||
}
|
||||
export -f sqlite3
|
||||
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
lsof() { return 0; } # Database is in use
|
||||
|
||||
opt_sqlite_vacuum
|
||||
echo "VACUUM_COUNT=$VACUUM_COUNT"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
# Should not VACUUM when database is in use
|
||||
[[ "$output" == *"VACUUM_COUNT=0"* ]]
|
||||
}
|
||||
|
||||
@test "opt_sqlite_vacuum checks disk space before VACUUM" {
|
||||
skip_if_no_sqlite3
|
||||
mkdir -p "$HOME/Library/Safari"
|
||||
|
||||
# Clean up and create a 2MB database
|
||||
rm -f "$HOME/Library/Safari/History.db"
|
||||
sqlite3 "$HOME/Library/Safari/History.db" << 'SQL'
|
||||
CREATE TABLE test(id INTEGER, data TEXT);
|
||||
INSERT INTO test VALUES(1, randomblob(1024*1024));
|
||||
INSERT INTO test VALUES(2, randomblob(1024*1024));
|
||||
SQL
|
||||
|
||||
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/optimize/tasks.sh"
|
||||
|
||||
VACUUM_ATTEMPTED=0
|
||||
original_sqlite3=$(command -v sqlite3)
|
||||
|
||||
sqlite3() {
|
||||
if [[ "$2" == "VACUUM;" ]]; then
|
||||
VACUUM_ATTEMPTED=1
|
||||
fi
|
||||
"$original_sqlite3" "$@"
|
||||
}
|
||||
export -f sqlite3
|
||||
|
||||
# Mock df to report insufficient space
|
||||
df() {
|
||||
if [[ "$1" == "-k" ]]; then
|
||||
echo "Filesystem 1024-blocks Used Available Capacity"
|
||||
echo "/dev/disk1 1000000 900000 100 90%" # Only 100KB free
|
||||
fi
|
||||
}
|
||||
export -f df
|
||||
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
lsof() { return 1; }
|
||||
|
||||
opt_sqlite_vacuum
|
||||
echo "VACUUM_ATTEMPTED=$VACUUM_ATTEMPTED"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
# Should skip VACUUM when insufficient disk space
|
||||
[[ "$output" == *"VACUUM_ATTEMPTED=0"* ]]
|
||||
}
|
||||
|
||||
@test "opt_sqlite_vacuum skips non-SQLite files" {
|
||||
skip_if_no_sqlite3
|
||||
mkdir -p "$HOME/Library/Safari"
|
||||
|
||||
# Create a non-SQLite file
|
||||
echo "not a database" > "$HOME/Library/Safari/History.db"
|
||||
|
||||
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/optimize/tasks.sh"
|
||||
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
|
||||
opt_sqlite_vacuum
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Databases already optimized"* ]]
|
||||
}
|
||||
|
||||
@test "opt_sqlite_vacuum verifies integrity after VACUUM" {
|
||||
skip_if_no_sqlite3
|
||||
mkdir -p "$HOME/Library/Safari"
|
||||
rm -f "$HOME/Library/Safari/History.db"
|
||||
sqlite3 "$HOME/Library/Safari/History.db" "CREATE TABLE test(id INTEGER); INSERT INTO test VALUES(1);"
|
||||
|
||||
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/optimize/tasks.sh"
|
||||
|
||||
CALL_LOG="$HOME/vacuum_calls.log"
|
||||
> "$CALL_LOG"
|
||||
|
||||
original_sqlite3=$(command -v sqlite3)
|
||||
|
||||
sqlite3() {
|
||||
if [[ "$2" == *"integrity_check"* ]]; then
|
||||
echo "INTEGRITY_CHECK" >> "$CALL_LOG"
|
||||
echo "ok"
|
||||
return 0
|
||||
elif [[ "$2" == "VACUUM;" ]]; then
|
||||
echo "VACUUM" >> "$CALL_LOG"
|
||||
fi
|
||||
"$original_sqlite3" "$@"
|
||||
}
|
||||
export -f sqlite3
|
||||
|
||||
start_inline_spinner() { :; }
|
||||
stop_inline_spinner() { :; }
|
||||
lsof() { return 1; }
|
||||
|
||||
opt_sqlite_vacuum
|
||||
|
||||
# Count calls
|
||||
integrity_count=$(grep -c "INTEGRITY_CHECK" "$CALL_LOG" || echo 0)
|
||||
vacuum_count=$(grep -c "VACUUM" "$CALL_LOG" || echo 0)
|
||||
|
||||
echo "INTEGRITY_CHECKS=$integrity_count"
|
||||
echo "VACUUM_COUNT=$vacuum_count"
|
||||
EOF
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
# Should check integrity at least once and perform VACUUM
|
||||
[[ "$output" == *"INTEGRITY_CHECKS="* ]]
|
||||
[[ "$output" == *"VACUUM_COUNT="* ]]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user