diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-25 01:07:31 -0400 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-25 01:07:31 -0400 |
| commit | 03a9ff353721ba8fb5f37cad2546d25788011451 (patch) | |
| tree | 9c98fb4fb20d9c11b894e30924f37d447c45d9c2 /tests/lib | |
| parent | 2e895830798d1685c35f24e595896b5caa4fdbcc (diff) | |
| download | rsyncshot-03a9ff353721ba8fb5f37cad2546d25788011451.tar.gz rsyncshot-03a9ff353721ba8fb5f37cad2546d25788011451.zip | |
fix: harden rsyncshot destination, rotation, and mount handling
Refuse to run when REMOTE_PATH or MOUNTDIR is empty or non-absolute. A blank config value otherwise resolves the destination to the filesystem root, where rotation runs rm -rf and --delete.
Run the rotation cp/mv/rm as bare command names in remote mode, resolved by the remote PATH, instead of hardcoded /usr/bin paths that broke on remotes whose binaries live elsewhere. Resolve the real binary via command -v in local mode.
Pass rsync -R so each source is stored under its full path. Two includes sharing a basename (/usr/local/bin and /usr/bin) previously both mapped to latest/bin, and the second sync's --delete wiped the first. This changes the stored layout: /usr/local/bin now lives at latest/usr/local/bin instead of latest/bin. /home and /etc are unchanged.
Add rsync --numeric-ids so /etc and /home ownership survives a restore when the destination has a different passwd/group database.
Match mount points exactly via is_mounted() instead of a substring grep of /proc/mounts, so /media/backup no longer matches /media/backup2, paths compare literally, and mount points with spaces work.
Reject a retention count below 1, which previously still created one snapshot. Drop the grep "|| echo 0" that emitted a stray second line, assemble ssh options as arrays, and quote the RSYNC_RSH identity path.
Extract derive_paths() and is_mounted() as sourceable functions and add unit, rsync-flag, and gated remote-mode test suites. The suite now runs 36 tests, and shellcheck is clean across the script and tests.
Diffstat (limited to 'tests/lib')
| -rwxr-xr-x | tests/lib/test_helpers.sh | 51 |
1 files changed, 47 insertions, 4 deletions
diff --git a/tests/lib/test_helpers.sh b/tests/lib/test_helpers.sh index 726d788..d58895b 100755 --- a/tests/lib/test_helpers.sh +++ b/tests/lib/test_helpers.sh @@ -6,6 +6,7 @@ # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' +# shellcheck disable=SC2034 # reserved for future use, kept alongside RED/GREEN YELLOW='\033[0;33m' NC='\033[0m' # No Color @@ -131,9 +132,9 @@ assert_exit_code() { assert_contains() { local haystack="$1" local needle="$2" - local message="${3:-Output should contain '$needle'}" + local message="${3:-Output should contain: $needle}" - if echo "$haystack" | grep -q "$needle"; then + if echo "$haystack" | grep -qF -- "$needle"; then return 0 else echo -e "${RED}FAIL${NC}: $message" @@ -146,9 +147,9 @@ assert_contains() { assert_not_contains() { local haystack="$1" local needle="$2" - local message="${3:-Output should not contain '$needle'}" + local message="${3:-Output should not contain: $needle}" - if ! echo "$haystack" | grep -q "$needle"; then + if ! echo "$haystack" | grep -qF -- "$needle"; then return 0 else echo -e "${RED}FAIL${NC}: $message" @@ -206,6 +207,48 @@ assert_dir_not_exists() { fi } +assert_no_stderr() { + local stderr_content="$1" + local message="${2:-Should produce no stderr}" + + if [ -z "$stderr_content" ]; then + return 0 + else + echo -e "${RED}FAIL${NC}: $message" + echo " stderr was: $stderr_content" + return 1 + fi +} + +# ------------------------------------------------------------------------------ +# Command-capture shim +# ------------------------------------------------------------------------------ +# make_command_shim CMD [CMD...] — create a directory of fake commands that +# record their argv (one argument per line, "---" between invocations) to +# <dir>/<cmd>.args and exit 0. Put the dir at the front of PATH to intercept +# those commands and assert on HOW they were called, without real I/O, network, +# or transfers. Echoes the shim directory path; the caller removes it. +# +# Usage: +# shim=$(make_command_shim rsync) +# PATH="$shim:$PATH" "$SCRIPT_PATH" dryrun manual 1 +# assert_contains "$(cat "$shim/rsync.args")" "--numeric-ids" ... +# rm -rf "$shim" +make_command_shim() { + local shim_dir + shim_dir=$(mktemp -d) + local cmd + for cmd in "$@"; do + cat > "$shim_dir/$cmd" <<SHIM +#!/usr/bin/env bash +{ printf '%s\n' "\$@"; echo "---"; } >> "$shim_dir/${cmd}.args" +exit 0 +SHIM + chmod +x "$shim_dir/$cmd" + done + echo "$shim_dir" +} + # ------------------------------------------------------------------------------ # Test Runner # ------------------------------------------------------------------------------ |
