1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-22 20:15:07 +00:00

fix: preserve interrupt semantics and restore purge traps

This commit is contained in:
tw93
2026-02-27 11:18:53 +08:00
parent 194fe871e5
commit a9433e4acd
5 changed files with 81 additions and 0 deletions

View File

@@ -121,6 +121,8 @@ Security-sensitive cleanup paths are covered by BATS regression tests, including
- `tests/clean_user_core.bats` - `tests/clean_user_core.bats`
- `tests/clean_dev_caches.bats` - `tests/clean_dev_caches.bats`
- `tests/clean_system_maintenance.bats` - `tests/clean_system_maintenance.bats`
- `tests/purge.bats`
- `tests/core_safe_functions.bats`
**System Memory Reports** computation uses bulk `find -exec stat` to avoid bash loop child-process limits on corrupted systems. **System Memory Reports** computation uses bulk `find -exec stat` to avoid bash loop child-process limits on corrupted systems.
`bin/clean.sh` dry-run export temp files rely on tracked temp lifecycle (`create_temp_file()` + trap cleanup) to avoid orphan temp artifacts. `bin/clean.sh` dry-run export temp files rely on tracked temp lifecycle (`create_temp_file()` + trap cleanup) to avoid orphan temp artifacts.
@@ -132,6 +134,7 @@ Latest local verification for this release branch:
- `bats tests/clean_user_core.bats` passed (13/13) - `bats tests/clean_user_core.bats` passed (13/13)
- `bats tests/clean_dev_caches.bats` passed (8/8) - `bats tests/clean_dev_caches.bats` passed (8/8)
- `bats tests/clean_system_maintenance.bats` passed (40/40) - `bats tests/clean_system_maintenance.bats` passed (40/40)
- `bats tests/purge.bats tests/core_safe_functions.bats` passed (67/67)
Run tests: Run tests:

View File

@@ -569,16 +569,38 @@ select_purge_categories() {
fi fi
done done
local original_stty="" local original_stty=""
local previous_exit_trap=""
local previous_int_trap=""
local previous_term_trap=""
local terminal_restored=false
if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then if [[ -t 0 ]] && command -v stty > /dev/null 2>&1; then
original_stty=$(stty -g 2> /dev/null || echo "") original_stty=$(stty -g 2> /dev/null || echo "")
fi fi
previous_exit_trap=$(trap -p EXIT || true)
previous_int_trap=$(trap -p INT || true)
previous_term_trap=$(trap -p TERM || true)
# Terminal control functions # Terminal control functions
restore_terminal() { restore_terminal() {
# Avoid trap churn when restore is called repeatedly via RETURN/EXIT paths.
if [[ "${terminal_restored:-false}" == "true" ]]; then
return
fi
terminal_restored=true
trap - EXIT INT TERM trap - EXIT INT TERM
show_cursor show_cursor
if [[ -n "${original_stty:-}" ]]; then if [[ -n "${original_stty:-}" ]]; then
stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || true stty "${original_stty}" 2> /dev/null || stty sane 2> /dev/null || true
fi fi
if [[ -n "$previous_exit_trap" ]]; then
eval "$previous_exit_trap"
fi
if [[ -n "$previous_int_trap" ]]; then
eval "$previous_int_trap"
fi
if [[ -n "$previous_term_trap" ]]; then
eval "$previous_term_trap"
fi
} }
# shellcheck disable=SC2329 # shellcheck disable=SC2329
handle_interrupt() { handle_interrupt() {

View File

@@ -249,6 +249,11 @@ safe_remove() {
local rm_exit=0 local rm_exit=0
error_msg=$(rm -rf "$path" 2>&1) || rm_exit=$? # safe_remove error_msg=$(rm -rf "$path" 2>&1) || rm_exit=$? # safe_remove
# Preserve interrupt semantics so callers can abort long-running deletions.
if [[ $rm_exit -ge 128 ]]; then
return "$rm_exit"
fi
if [[ $rm_exit -eq 0 ]]; then if [[ $rm_exit -eq 0 ]]; then
# Log successful removal # Log successful removal
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human" log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"

View File

@@ -110,6 +110,19 @@ teardown() {
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
@test "safe_remove preserves interrupt exit codes" {
local test_file="$TEST_DIR/interrupt_file"
echo "test" > "$test_file"
run bash -c "
source '$PROJECT_ROOT/lib/core/common.sh'
rm() { return 130; }
safe_remove '$test_file' true
"
[ "$status" -eq 130 ]
[ -f "$test_file" ]
}
@test "safe_remove in silent mode suppresses error output" { @test "safe_remove in silent mode suppresses error output" {
run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '/System/test' true 2>&1" run bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; safe_remove '/System/test' true 2>&1"
[ "$status" -eq 1 ] [ "$status" -eq 1 ]

View File

@@ -308,6 +308,44 @@ EOF
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
} }
@test "select_purge_categories restores caller EXIT/INT/TERM traps" {
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
set -euo pipefail
source "$PROJECT_ROOT/lib/clean/project.sh"
trap 'echo parent-exit' EXIT
trap 'echo parent-int' INT
trap 'echo parent-term' TERM
before_exit=$(trap -p EXIT)
before_int=$(trap -p INT)
before_term=$(trap -p TERM)
PURGE_CATEGORY_SIZES="1"
PURGE_RECENT_CATEGORIES="false"
select_purge_categories "demo" <<< $'\n' > /dev/null 2>&1 || true
after_exit=$(trap -p EXIT)
after_int=$(trap -p INT)
after_term=$(trap -p TERM)
if [[ "$before_exit" == "$after_exit" && "$before_int" == "$after_int" && "$before_term" == "$after_term" ]]; then
echo "PASS"
else
echo "FAIL"
echo "before_exit=$before_exit"
echo "after_exit=$after_exit"
echo "before_int=$before_int"
echo "after_int=$after_int"
echo "before_term=$before_term"
echo "after_term=$after_term"
exit 1
fi
EOF
[ "$status" -eq 0 ]
[[ "$output" == *"PASS"* ]]
}
@test "confirm_purge_cleanup accepts Enter" { @test "confirm_purge_cleanup accepts Enter" {
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