diff options
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/test-zfs-snap-prune.sh | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/scripts/test-zfs-snap-prune.sh b/scripts/test-zfs-snap-prune.sh new file mode 100755 index 0000000..d59a7cc --- /dev/null +++ b/scripts/test-zfs-snap-prune.sh @@ -0,0 +1,303 @@ +#!/bin/bash +# test-zfs-snap-prune.sh - Comprehensive test suite for zfs-snap-prune +# +# Runs various scenarios with mock data to verify the pruning logic. +# No root or ZFS required - uses --test mode with mock data. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PRUNE_SCRIPT="$SCRIPT_DIR/../custom/zfs-snap-prune" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Test counters - use temp files to avoid subshell issues with pipes +COUNTER_FILE=$(mktemp) +echo "0 0 0" > "$COUNTER_FILE" # run passed failed +trap "rm -f $COUNTER_FILE" EXIT + +# Time constants for generating test data +DAY=$((24 * 60 * 60)) +NOW=$(date +%s) + +# Generate a snapshot line for mock data +# Args: snapshot_name days_ago +# Output: zroot/ROOT/default@name<TAB>epoch_timestamp +make_snap() { + local name="$1" + local days_ago="$2" + local timestamp=$((NOW - (days_ago * DAY))) + echo -e "zroot/ROOT/default@${name}\t${timestamp}" +} + +# Generate N snapshots with given prefix and starting age +# Args: prefix count start_days_ago +# NOTE: Outputs oldest first (like ZFS with -s creation), so start_age is the OLDEST +make_snaps() { + local prefix="$1" + local count="$2" + local start_age="$3" + # Generate from oldest to newest (ZFS order with -s creation) + for ((i=count; i>=1; i--)); do + make_snap "${prefix}_${i}" $((start_age + i - 1)) + done +} + +# Increment counter in temp file +# Args: position (1=run, 2=passed, 3=failed) +inc_counter() { + local pos="$1" + local counters + read -r run passed failed < "$COUNTER_FILE" + case "$pos" in + 1) run=$((run + 1)) ;; + 2) passed=$((passed + 1)) ;; + 3) failed=$((failed + 1)) ;; + esac + echo "$run $passed $failed" > "$COUNTER_FILE" +} + +# Get counter values +get_counters() { + cat "$COUNTER_FILE" +} + +# Run a test case +# Args: test_name expected_kept expected_deleted env_vars +# Reads mock snapshot data from stdin +run_test() { + local test_name="$1" + local expected_kept="$2" + local expected_deleted="$3" + shift 3 + local env_vars="$*" + + inc_counter 1 # TESTS_RUN++ + echo -e "${BLUE}TEST:${NC} $test_name" + + # Capture stdin (mock data) and pass to prune script + local mock_data + mock_data=$(cat) + + # Run prune script with mock data on stdin + local output + output=$(echo "$mock_data" | env NOW_OVERRIDE="$NOW" $env_vars "$PRUNE_SCRIPT" --test --quiet 2>&1) + + # Extract results + local result_line + result_line=$(echo "$output" | grep "^RESULT:" || echo "RESULT:kept=0,deleted=0") + local actual_kept + actual_kept=$(echo "$result_line" | sed 's/.*kept=\([0-9]*\).*/\1/') + local actual_deleted + actual_deleted=$(echo "$result_line" | sed 's/.*deleted=\([0-9]*\).*/\1/') + + # Compare + if [[ "$actual_kept" == "$expected_kept" ]] && [[ "$actual_deleted" == "$expected_deleted" ]]; then + echo -e " ${GREEN}PASS${NC} (kept=$actual_kept, deleted=$actual_deleted)" + inc_counter 2 # TESTS_PASSED++ + return 0 + else + echo -e " ${RED}FAIL${NC}" + echo -e " Expected: kept=$expected_kept, deleted=$expected_deleted" + echo -e " Actual: kept=$actual_kept, deleted=$actual_deleted" + inc_counter 3 # TESTS_FAILED++ + return 1 + fi +} + +# Print section header +section() { + echo "" + echo -e "${YELLOW}=== $1 ===${NC}" +} + +# Verify prune script exists +if [[ ! -x "$PRUNE_SCRIPT" ]]; then + chmod +x "$PRUNE_SCRIPT" +fi + +echo -e "${GREEN}zfs-snap-prune Test Suite${NC}" +echo "=========================" +echo "Using NOW=$NOW ($(date -d "@$NOW" '+%Y-%m-%d %H:%M:%S'))" +echo "Default policy: KEEP_COUNT=20, MAX_AGE_DAYS=180" + +############################################################################### +section "Basic Cases" +############################################################################### + +# Test 1: Empty list +echo -n "" | run_test "Empty snapshot list" 0 0 + +# Test 2: Single snapshot +make_snap "test1" 5 | run_test "Single snapshot (recent)" 1 0 + +# Test 3: Under keep count - all recent +make_snaps "recent" 10 1 | run_test "10 snapshots, all recent" 10 0 + +# Test 4: Exactly at keep count +make_snaps "exact" 20 1 | run_test "Exactly 20 snapshots" 20 0 + +############################################################################### +section "Over Keep Count - Age Matters" +############################################################################### + +# Test 5: 25 snapshots, all recent (within 180 days) +# Should keep all - none old enough to delete +make_snaps "recent" 25 1 | run_test "25 snapshots, all recent (<180 days)" 25 0 + +# Test 6: 25 snapshots, 5 are old (>180 days) +# First 20 (most recent) kept by count, 5 oldest are >180 days old, so deleted +{ + make_snaps "old" 5 200 # oldest first (200-204 days ago) + make_snaps "recent" 20 1 # newest last (1-20 days ago) +} | run_test "25 snapshots, 5 old (>180 days) - delete 5" 20 5 + +# Test 7: 30 snapshots, 10 beyond limit but only 5 old enough +{ + make_snaps "old" 5 200 # 200-204 days old - delete these + make_snaps "medium" 5 100 # 100-104 days old - not old enough + make_snaps "recent" 20 1 # 1-20 days old +} | run_test "30 snapshots, 5 medium age, 5 old - delete 5" 25 5 + +############################################################################### +section "Genesis Protection" +############################################################################### + +# Test 8: Genesis at position 21, old - should NOT be deleted +{ + make_snap "genesis" 365 # oldest: 1 year old + make_snaps "recent" 20 1 # newest: 1-20 days ago +} | run_test "Genesis at position 21 (old) - protected" 21 0 + +# Test 9: Genesis at position 25, with other old snapshots +{ + make_snap "genesis" 365 # oldest: protected + make_snaps "old" 4 200 # 200-203 days old - should be deleted + make_snaps "recent" 20 1 # 1-20 days old +} | run_test "Genesis at position 25 with 4 old - delete 4, keep genesis" 21 4 + +# Test 10: Genesis within keep count (20 total snapshots) +{ + make_snap "genesis" 365 # oldest + make_snaps "more" 9 15 # 15-23 days ago + make_snaps "recent" 10 1 # 1-10 days ago +} | run_test "Genesis at position 11 (within keep count)" 20 0 + +############################################################################### +section "Custom Configuration" +############################################################################### + +# Test 11: Custom KEEP_COUNT=5 +make_snaps "test" 10 200 | \ + run_test "KEEP_COUNT=5, 10 old snapshots - delete 5" 5 5 KEEP_COUNT=5 + +# Test 12: Custom MAX_AGE_DAYS=30 +{ + make_snaps "medium" 5 50 # 50-54 days old - now considered old with MAX_AGE=30 + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "MAX_AGE_DAYS=30, 5 snapshots >30 days - delete 5" 20 5 MAX_AGE_DAYS=30 + +# Test 13: Very short retention +make_snaps "test" 15 10 | \ + run_test "KEEP_COUNT=3, MAX_AGE=7, 15 snaps (10+ days old) - delete 12" 3 12 KEEP_COUNT=3 MAX_AGE_DAYS=7 + +# Test 14: Relaxed retention - nothing deleted +make_snaps "test" 50 1 | \ + run_test "KEEP_COUNT=100 - keep all 50" 50 0 KEEP_COUNT=100 + +############################################################################### +section "Edge Cases" +############################################################################### + +# Test 15: Snapshot exactly at MAX_AGE boundary (180 days) - should be kept +{ + make_snap "boundary" 180 # Exactly 180 days - >= cutoff, kept + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "1 snapshot exactly at 180 day boundary - keep" 21 0 + +# Test 16: Snapshot just over MAX_AGE boundary (181 days) - should be deleted +{ + make_snap "over" 181 # 181 days - just over, should be deleted + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "1 snapshot at 181 days - delete" 20 1 + +# Test 17: Mixed boundary - some at 180, some at 181 +{ + make_snap "over2" 182 # deleted + make_snap "over1" 181 # deleted + make_snap "boundary" 180 # kept (exactly at cutoff) + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "2 over boundary, 1 at boundary - delete 2" 21 2 + +# Test 18: Mixed naming patterns (ordered oldest to newest) +{ + make_snap "genesis" 365 + make_snap "before-upgrade" 20 + make_snap "manual_backup" 15 + make_snap "pre-pacman_2025-01-10" 10 + make_snap "pre-pacman_2025-01-15" 5 +} | run_test "Mixed snapshot names (5 total)" 5 0 + +# Test 19: Large number of snapshots +{ + make_snaps "old" 100 200 # 200-299 days old + make_snaps "recent" 20 1 # 1-20 days ago +} | run_test "120 snapshots, 100 old - delete 100" 20 100 + +############################################################################### +section "Realistic Scenarios" +############################################################################### + +# Test 20: One year of weekly pacman updates + genesis +# 52 snapshots (one per week) + genesis +# Ordered oldest first: genesis (365 days), then week_51 (357 days), ..., week_0 (0 days) +{ + make_snap "genesis" 365 + for ((week=51; week>=0; week--)); do + make_snap "pre-pacman_week_${week}" $((week * 7)) + done +} | run_test "1 year of weekly updates (52) + genesis" 27 26 +# Analysis (after tac, newest first): +# Position 1-20: week_0 through week_19 (0-133 days) - kept by count +# Position 21-26: week_20 through week_25 (140-175 days) - kept by age (<180) +# Position 27-52: week_26 through week_51 (182-357 days) - deleted (>180) +# Position 53: genesis (365 days) - protected +# Kept: 20 + 6 + 1 = 27, Deleted: 26 + +# Test 21: Fresh install with only genesis +make_snap "genesis" 1 | run_test "Fresh install - only genesis" 1 0 + +# Test 22: Burst of manual snapshots before big change +{ + make_snap "genesis" 30 + make_snap "before-nvidia" 20 + make_snap "before-DE-change" 19 + make_snaps "pre-pacman" 18 1 +} | run_test "20 snaps + genesis (30 days old)" 21 0 + +############################################################################### +section "Results" +############################################################################### + +# Read final counters +read -r TESTS_RUN TESTS_PASSED TESTS_FAILED < "$COUNTER_FILE" + +echo "" +echo "=========================" +echo -e "Tests run: $TESTS_RUN" +echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}" +echo -e "Tests failed: ${RED}$TESTS_FAILED${NC}" +echo "" + +if [[ $TESTS_FAILED -eq 0 ]]; then + echo -e "${GREEN}All tests passed!${NC}" + exit 0 +else + echo -e "${RED}Some tests failed.${NC}" + exit 1 +fi |
