summaryrefslogtreecommitdiff
path: root/corebinutils/mv/tests
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:27:17 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:27:17 +0300
commit5086370dd0d4e581d1f5e4b8a085cd421eb23235 (patch)
tree81965e5c254be9f4ff8f438dac161c7ce0009175 /corebinutils/mv/tests
parenteabb006d617365cd92c3337d673af7fc6a210d78 (diff)
parenteef48ae3a05a5829217d2b6a8e74c0cb5f978cf3 (diff)
downloadProject-Tick-5086370dd0d4e581d1f5e4b8a085cd421eb23235.tar.gz
Project-Tick-5086370dd0d4e581d1f5e4b8a085cd421eb23235.zip
Add 'corebinutils/mv/' from commit 'eef48ae3a05a5829217d2b6a8e74c0cb5f978cf3'
git-subtree-dir: corebinutils/mv git-subtree-mainline: eabb006d617365cd92c3337d673af7fc6a210d78 git-subtree-split: eef48ae3a05a5829217d2b6a8e74c0cb5f978cf3
Diffstat (limited to 'corebinutils/mv/tests')
-rw-r--r--corebinutils/mv/tests/test.sh414
1 files changed, 414 insertions, 0 deletions
diff --git a/corebinutils/mv/tests/test.sh b/corebinutils/mv/tests/test.sh
new file mode 100644
index 0000000000..c2cd08c8a8
--- /dev/null
+++ b/corebinutils/mv/tests/test.sh
@@ -0,0 +1,414 @@
+#!/bin/sh
+set -eu
+
+ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
+MV_BIN=${MV_BIN:-"$ROOT/out/mv"}
+TMPDIR=${TMPDIR:-/tmp}
+WORKDIR=$(mktemp -d "$TMPDIR/mv-test.XXXXXX")
+STDOUT_FILE="$WORKDIR/stdout"
+STDERR_FILE="$WORKDIR/stderr"
+LAST_STATUS=0
+LAST_STDOUT=
+LAST_STDERR=
+ALTROOT=
+
+cleanup() {
+ rm -rf "$WORKDIR"
+ if [ -n "${ALTROOT:-}" ] && [ -d "$ALTROOT" ]; then
+ rm -rf "$ALTROOT"
+ fi
+}
+trap cleanup EXIT INT TERM
+
+export LC_ALL=C
+
+MV_USAGE=$(cat <<'EOF'
+usage: mv [-f | -i | -n] [-hv] source target
+ mv [-f | -i | -n] [-v] source ... directory
+EOF
+)
+
+fail() {
+ printf '%s\n' "FAIL: $1" >&2
+ exit 1
+}
+
+assert_eq() {
+ name=$1
+ expected=$2
+ actual=$3
+ if [ "$expected" != "$actual" ]; then
+ printf '%s\n' "FAIL: $name" >&2
+ printf '%s\n' "--- expected ---" >&2
+ printf '%s' "$expected" >&2
+ printf '\n%s\n' "--- actual ---" >&2
+ printf '%s' "$actual" >&2
+ printf '\n' >&2
+ exit 1
+ fi
+}
+
+assert_contains() {
+ name=$1
+ text=$2
+ pattern=$3
+ case $text in
+ *"$pattern"*) ;;
+ *) fail "$name" ;;
+ esac
+}
+
+assert_empty() {
+ name=$1
+ text=$2
+ [ -z "$text" ] || fail "$name"
+}
+
+assert_exists() {
+ path=$1
+ [ -e "$path" ] || fail "missing path: $path"
+}
+
+assert_not_exists() {
+ path=$1
+ [ ! -e "$path" ] || fail "unexpected path: $path"
+}
+
+assert_status() {
+ name=$1
+ expected=$2
+ actual=$3
+ [ "$expected" -eq "$actual" ] || fail "$name"
+}
+
+assert_file_text() {
+ expected=$1
+ path=$2
+ actual=$(cat "$path")
+ [ "$actual" = "$expected" ] || fail "content mismatch: $path"
+}
+
+assert_mode() {
+ expected=$1
+ path=$2
+ actual=$(stat -c '%a' "$path")
+ [ "$actual" = "$expected" ] || fail "mode mismatch: $path"
+}
+
+assert_symlink_target() {
+ expected=$1
+ path=$2
+ [ -L "$path" ] || fail "not a symlink: $path"
+ actual=$(readlink "$path")
+ [ "$actual" = "$expected" ] || fail "symlink mismatch: $path"
+}
+
+inode_key() {
+ stat -c '%d:%i' "$1"
+}
+
+assert_same_inode() {
+ path1=$1
+ path2=$2
+ [ "$(inode_key "$path1")" = "$(inode_key "$path2")" ] || \
+ fail "inode mismatch: $path1 $path2"
+}
+
+assert_different_inode() {
+ path1=$1
+ path2=$2
+ [ "$(inode_key "$path1")" != "$(inode_key "$path2")" ] || \
+ fail "unexpected shared inode: $path1 $path2"
+}
+
+run_capture() {
+ if "$@" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then
+ LAST_STATUS=0
+ else
+ LAST_STATUS=$?
+ fi
+ LAST_STDOUT=$(cat "$STDOUT_FILE")
+ LAST_STDERR=$(cat "$STDERR_FILE")
+}
+
+find_alt_parent() {
+ base_dev=$(stat -c '%d' "$WORKDIR")
+ uid=$(id -u)
+
+ for candidate in /dev/shm "$TMPDIR" /tmp /var/tmp "/run/user/$uid"; do
+ [ -d "$candidate" ] || continue
+ [ -w "$candidate" ] || continue
+ probe=$(mktemp -d "$candidate/mv-test-probe.XXXXXX" 2>/dev/null) || continue
+ probe_dev=$(stat -c '%d' "$probe" 2>/dev/null || true)
+ rm -rf "$probe"
+ [ -n "$probe_dev" ] || continue
+ if [ "$probe_dev" != "$base_dev" ]; then
+ printf '%s\n' "$candidate"
+ return 0
+ fi
+ done
+ return 1
+}
+
+setup_cross_device_root() {
+ alt_parent=$(find_alt_parent) || return 1
+ ALTROOT=$(mktemp -d "$alt_parent/mv-test-fs.XXXXXX")
+ return 0
+}
+
+[ -x "$MV_BIN" ] || fail "missing binary: $MV_BIN"
+
+run_capture "$MV_BIN"
+assert_status "usage status" 2 "$LAST_STATUS"
+assert_empty "usage stdout" "$LAST_STDOUT"
+assert_eq "usage stderr" "$MV_USAGE" "$LAST_STDERR"
+
+run_capture "$MV_BIN" -h a b c
+assert_status "invalid -h usage status" 2 "$LAST_STATUS"
+assert_empty "invalid -h usage stdout" "$LAST_STDOUT"
+assert_eq "invalid -h usage stderr" "$MV_USAGE" "$LAST_STDERR"
+
+mkdir "$WORKDIR/basic"
+printf 'hello\n' >"$WORKDIR/basic/src"
+run_capture "$MV_BIN" "$WORKDIR/basic/src" "$WORKDIR/basic/dst"
+assert_status "basic rename status" 0 "$LAST_STATUS"
+assert_empty "basic rename stdout" "$LAST_STDOUT"
+assert_empty "basic rename stderr" "$LAST_STDERR"
+assert_file_text "hello" "$WORKDIR/basic/dst"
+assert_not_exists "$WORKDIR/basic/src"
+
+mkdir "$WORKDIR/into-dir"
+printf 'alpha\n' >"$WORKDIR/into-dir/src"
+mkdir "$WORKDIR/into-dir/out"
+run_capture "$MV_BIN" "$WORKDIR/into-dir/src" "$WORKDIR/into-dir/out"
+assert_status "move into dir status" 0 "$LAST_STATUS"
+assert_file_text "alpha" "$WORKDIR/into-dir/out/src"
+assert_not_exists "$WORKDIR/into-dir/src"
+
+mkdir "$WORKDIR/hflag"
+mkdir "$WORKDIR/hflag/actual"
+ln -s actual "$WORKDIR/hflag/linkdir"
+printf 'first\n' >"$WORKDIR/hflag/src1"
+run_capture "$MV_BIN" "$WORKDIR/hflag/src1" "$WORKDIR/hflag/linkdir"
+assert_status "default symlink-dir target status" 0 "$LAST_STATUS"
+assert_file_text "first" "$WORKDIR/hflag/actual/src1"
+printf 'second\n' >"$WORKDIR/hflag/src2"
+run_capture "$MV_BIN" -h "$WORKDIR/hflag/src2" "$WORKDIR/hflag/linkdir"
+assert_status "h replace symlink status" 0 "$LAST_STATUS"
+assert_file_text "second" "$WORKDIR/hflag/linkdir"
+[ ! -L "$WORKDIR/hflag/linkdir" ] || fail "-h did not replace symlink target"
+
+mkdir "$WORKDIR/nflag"
+printf 'source\n' >"$WORKDIR/nflag/src"
+printf 'target\n' >"$WORKDIR/nflag/dst"
+run_capture "$MV_BIN" -n "$WORKDIR/nflag/src" "$WORKDIR/nflag/dst"
+assert_status "n status" 0 "$LAST_STATUS"
+assert_empty "n stdout" "$LAST_STDOUT"
+assert_empty "n stderr" "$LAST_STDERR"
+assert_file_text "source" "$WORKDIR/nflag/src"
+assert_file_text "target" "$WORKDIR/nflag/dst"
+
+printf 'source\n' >"$WORKDIR/nflag/src2"
+printf 'target\n' >"$WORKDIR/nflag/dst2"
+run_capture "$MV_BIN" -vn "$WORKDIR/nflag/src2" "$WORKDIR/nflag/dst2"
+assert_status "vn status" 0 "$LAST_STATUS"
+assert_eq "vn stdout" "$WORKDIR/nflag/dst2 not overwritten" "$LAST_STDOUT"
+assert_empty "vn stderr" "$LAST_STDERR"
+assert_file_text "source" "$WORKDIR/nflag/src2"
+assert_file_text "target" "$WORKDIR/nflag/dst2"
+
+mkdir "$WORKDIR/interactive-no"
+printf 'source\n' >"$WORKDIR/interactive-no/src"
+printf 'target\n' >"$WORKDIR/interactive-no/dst"
+if printf 'n\n' | "$MV_BIN" -i "$WORKDIR/interactive-no/src" \
+ "$WORKDIR/interactive-no/dst" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then
+ LAST_STATUS=0
+else
+ LAST_STATUS=$?
+fi
+LAST_STDOUT=$(cat "$STDOUT_FILE")
+LAST_STDERR=$(cat "$STDERR_FILE")
+assert_status "interactive no status" 0 "$LAST_STATUS"
+assert_empty "interactive no stdout" "$LAST_STDOUT"
+assert_contains "interactive no prompt" "$LAST_STDERR" \
+ "overwrite $WORKDIR/interactive-no/dst? (y/n [n]) "
+assert_contains "interactive no rejection" "$LAST_STDERR" "not overwritten"
+assert_file_text "source" "$WORKDIR/interactive-no/src"
+assert_file_text "target" "$WORKDIR/interactive-no/dst"
+
+mkdir "$WORKDIR/interactive-yes"
+printf 'source\n' >"$WORKDIR/interactive-yes/src"
+printf 'target\n' >"$WORKDIR/interactive-yes/dst"
+if printf 'y\n' | "$MV_BIN" -i "$WORKDIR/interactive-yes/src" \
+ "$WORKDIR/interactive-yes/dst" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then
+ LAST_STATUS=0
+else
+ LAST_STATUS=$?
+fi
+LAST_STDOUT=$(cat "$STDOUT_FILE")
+LAST_STDERR=$(cat "$STDERR_FILE")
+assert_status "interactive yes status" 0 "$LAST_STATUS"
+assert_empty "interactive yes stdout" "$LAST_STDOUT"
+assert_contains "interactive yes prompt" "$LAST_STDERR" \
+ "overwrite $WORKDIR/interactive-yes/dst? (y/n [n]) "
+assert_not_exists "$WORKDIR/interactive-yes/src"
+assert_file_text "source" "$WORKDIR/interactive-yes/dst"
+
+mkdir "$WORKDIR/f-precedence"
+printf 'source\n' >"$WORKDIR/f-precedence/src"
+printf 'target\n' >"$WORKDIR/f-precedence/dst"
+run_capture "$MV_BIN" -i -n -f "$WORKDIR/f-precedence/src" "$WORKDIR/f-precedence/dst"
+assert_status "force precedence status" 0 "$LAST_STATUS"
+assert_empty "force precedence stderr" "$LAST_STDERR"
+assert_file_text "source" "$WORKDIR/f-precedence/dst"
+assert_not_exists "$WORKDIR/f-precedence/src"
+
+mkdir "$WORKDIR/dir-to-file"
+mkdir "$WORKDIR/dir-to-file/src"
+printf 'payload\n' >"$WORKDIR/dir-to-file/src/file"
+printf 'target\n' >"$WORKDIR/dir-to-file/dst"
+run_capture "$MV_BIN" "$WORKDIR/dir-to-file/src" "$WORKDIR/dir-to-file/dst"
+assert_status "dir-to-file status" 1 "$LAST_STATUS"
+assert_contains "dir-to-file stderr" "$LAST_STDERR" "Not a directory"
+assert_exists "$WORKDIR/dir-to-file/src/file"
+
+mkdir "$WORKDIR/file-to-dir"
+printf 'payload\n' >"$WORKDIR/file-to-dir/src"
+mkdir -p "$WORKDIR/file-to-dir/out/src"
+run_capture "$MV_BIN" "$WORKDIR/file-to-dir/src" "$WORKDIR/file-to-dir/out"
+assert_status "file-to-dir final dir status" 1 "$LAST_STATUS"
+assert_contains "file-to-dir stderr" "$LAST_STDERR" "Is a directory"
+assert_exists "$WORKDIR/file-to-dir/src"
+
+mkdir "$WORKDIR/verbose"
+printf 'payload\n' >"$WORKDIR/verbose/src"
+mkdir "$WORKDIR/verbose/out"
+run_capture "$MV_BIN" -v "$WORKDIR/verbose/src" "$WORKDIR/verbose/out"
+assert_status "verbose status" 0 "$LAST_STATUS"
+assert_eq "verbose stdout" \
+ "$WORKDIR/verbose/src -> $WORKDIR/verbose/out/src" "$LAST_STDOUT"
+assert_empty "verbose stderr" "$LAST_STDERR"
+
+mkdir "$WORKDIR/fifo"
+mkfifo "$WORKDIR/fifo/src"
+run_capture "$MV_BIN" "$WORKDIR/fifo/src" "$WORKDIR/fifo/dst"
+assert_status "fifo rename status" 0 "$LAST_STATUS"
+[ -p "$WORKDIR/fifo/dst" ] || fail "fifo move lost fifo type"
+assert_not_exists "$WORKDIR/fifo/src"
+
+if setup_cross_device_root; then
+ mkdir "$WORKDIR/cross-file"
+ printf 'cross-data\n' >"$WORKDIR/cross-file/src"
+ chmod 754 "$WORKDIR/cross-file/src"
+ touch -t 202001020304.05 "$WORKDIR/cross-file/src"
+ src_mtime=$(stat -c '%Y' "$WORKDIR/cross-file/src")
+ run_capture "$MV_BIN" "$WORKDIR/cross-file/src" "$ALTROOT/regular"
+ assert_status "cross regular status" 0 "$LAST_STATUS"
+ assert_file_text "cross-data" "$ALTROOT/regular"
+ assert_mode "754" "$ALTROOT/regular"
+ [ "$(stat -c '%Y' "$ALTROOT/regular")" = "$src_mtime" ] || fail "mtime mismatch after cross-device regular move"
+ assert_not_exists "$WORKDIR/cross-file/src"
+
+ mkdir "$WORKDIR/cross-link"
+ printf 'target\n' >"$WORKDIR/cross-link/data"
+ ln -s data "$WORKDIR/cross-link/src"
+ run_capture "$MV_BIN" "$WORKDIR/cross-link/src" "$ALTROOT/link"
+ assert_status "cross symlink status" 0 "$LAST_STATUS"
+ assert_symlink_target "data" "$ALTROOT/link"
+ assert_not_exists "$WORKDIR/cross-link/src"
+
+ mkdir "$WORKDIR/cross-tree"
+ mkdir -p "$WORKDIR/cross-tree/src/sub"
+ printf 'nested\n' >"$WORKDIR/cross-tree/src/sub/file"
+ ln -s sub/file "$WORKDIR/cross-tree/src/link"
+ mkfifo "$WORKDIR/cross-tree/src/pipe"
+ chmod 711 "$WORKDIR/cross-tree/src/sub"
+ run_capture "$MV_BIN" "$WORKDIR/cross-tree/src" "$ALTROOT"
+ assert_status "cross tree status" 0 "$LAST_STATUS"
+ assert_file_text "nested" "$ALTROOT/src/sub/file"
+ assert_symlink_target "sub/file" "$ALTROOT/src/link"
+ [ -p "$ALTROOT/src/pipe" ] || fail "cross tree fifo missing"
+ assert_mode "711" "$ALTROOT/src/sub"
+ assert_not_exists "$WORKDIR/cross-tree/src"
+
+ mkdir "$WORKDIR/cross-empty"
+ mkdir -p "$WORKDIR/cross-empty/src/child"
+ printf 'replacement\n' >"$WORKDIR/cross-empty/src/child/file"
+ mkdir -p "$ALTROOT/replace-empty/src"
+ run_capture "$MV_BIN" "$WORKDIR/cross-empty/src" "$ALTROOT/replace-empty"
+ assert_status "cross replace empty dir status" 0 "$LAST_STATUS"
+ assert_file_text "replacement" "$ALTROOT/replace-empty/src/child/file"
+ assert_not_exists "$WORKDIR/cross-empty/src"
+
+ mkdir "$WORKDIR/cross-nonempty"
+ mkdir -p "$WORKDIR/cross-nonempty/src/child"
+ printf 'source\n' >"$WORKDIR/cross-nonempty/src/child/file"
+ mkdir -p "$ALTROOT/replace-nonempty/src"
+ printf 'keep\n' >"$ALTROOT/replace-nonempty/src/existing"
+ run_capture "$MV_BIN" "$WORKDIR/cross-nonempty/src" "$ALTROOT/replace-nonempty"
+ assert_status "cross replace nonempty dir status" 1 "$LAST_STATUS"
+ assert_contains "cross replace nonempty dir stderr" "$LAST_STDERR" \
+ "Directory not empty"
+ assert_exists "$WORKDIR/cross-nonempty/src/child/file"
+ assert_file_text "keep" "$ALTROOT/replace-nonempty/src/existing"
+
+ mkdir "$WORKDIR/cross-fifo"
+ mkfifo "$WORKDIR/cross-fifo/src"
+ run_capture "$MV_BIN" "$WORKDIR/cross-fifo/src" "$ALTROOT/fifo"
+ assert_status "cross fifo status" 0 "$LAST_STATUS"
+ [ -p "$ALTROOT/fifo" ] || fail "cross-device fifo missing"
+ assert_not_exists "$WORKDIR/cross-fifo/src"
+
+ mkdir "$WORKDIR/cross-hardlinks"
+ mkdir "$WORKDIR/cross-hardlinks/hardlinks-src"
+ printf 'shared\n' >"$WORKDIR/cross-hardlinks/hardlinks-src/a"
+ ln "$WORKDIR/cross-hardlinks/hardlinks-src/a" \
+ "$WORKDIR/cross-hardlinks/hardlinks-src/b"
+ assert_same_inode "$WORKDIR/cross-hardlinks/hardlinks-src/a" \
+ "$WORKDIR/cross-hardlinks/hardlinks-src/b"
+ run_capture "$MV_BIN" "$WORKDIR/cross-hardlinks/hardlinks-src" "$ALTROOT"
+ assert_status "cross hardlink tree status" 0 "$LAST_STATUS"
+ assert_file_text "shared" "$ALTROOT/hardlinks-src/a"
+ assert_file_text "shared" "$ALTROOT/hardlinks-src/b"
+ assert_different_inode "$ALTROOT/hardlinks-src/a" \
+ "$ALTROOT/hardlinks-src/b"
+ assert_not_exists "$WORKDIR/cross-hardlinks/hardlinks-src"
+
+ alt_avail_kb=$(df -Pk "$ALTROOT" | awk 'NR==2 { print $4 }')
+ case $alt_avail_kb in
+ ''|*[!0-9]*) alt_avail_kb=0 ;;
+ esac
+ if [ "$alt_avail_kb" -ge 4096 ] && [ "$alt_avail_kb" -le 131072 ]; then
+ mkdir "$WORKDIR/cross-partial"
+ dd if=/dev/zero of="$WORKDIR/cross-partial/src" bs=1024 count=2048 \
+ >"$STDOUT_FILE" 2>"$STDERR_FILE" || fail "partial source creation failed"
+ fill_kb=$((alt_avail_kb - 1024))
+ if [ "$fill_kb" -gt 0 ] && dd if=/dev/zero of="$ALTROOT/.fill-enospc" \
+ bs=1024 count="$fill_kb" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then
+ run_capture "$MV_BIN" "$WORKDIR/cross-partial/src" \
+ "$ALTROOT/partial-target"
+ [ "$LAST_STATUS" -ne 0 ] || fail "cross partial failure status"
+ assert_contains "cross partial failure stderr" "$LAST_STDERR" \
+ "No space left on device"
+ assert_exists "$WORKDIR/cross-partial/src"
+ assert_not_exists "$ALTROOT/partial-target"
+ fi
+ fi
+
+ if command -v setfattr >/dev/null 2>&1 && command -v getfattr >/dev/null 2>&1; then
+ printf 'probe\n' >"$WORKDIR/xattr-probe"
+ printf 'probe\n' >"$ALTROOT/xattr-probe"
+ if setfattr -n user.project_tick -v probe "$WORKDIR/xattr-probe" 2>/dev/null &&
+ setfattr -n user.project_tick -v probe "$ALTROOT/xattr-probe" 2>/dev/null; then
+ printf 'payload\n' >"$WORKDIR/xattr-src"
+ setfattr -n user.project_tick -v value "$WORKDIR/xattr-src"
+ run_capture "$MV_BIN" "$WORKDIR/xattr-src" "$ALTROOT/xattr-dst"
+ assert_status "cross xattr status" 0 "$LAST_STATUS"
+ xattr_value=$(getfattr --only-values -n user.project_tick "$ALTROOT/xattr-dst" 2>/dev/null)
+ assert_eq "cross xattr value" "value" "$xattr_value"
+ assert_not_exists "$WORKDIR/xattr-src"
+ fi
+ fi
+fi
+
+printf '%s\n' "PASS"