aboutsummaryrefslogtreecommitdiffstats
path: root/tests/zfs-tests/include
diff options
context:
space:
mode:
authorAleksa Sarai <[email protected]>2022-03-02 05:05:32 +1100
committerGitHub <[email protected]>2022-03-01 10:05:32 -0800
commit669683c4cbcd9c20258ce6641c6c777f29aeb94d (patch)
tree909474b14dbdfe47a345f9b2ad8bb02a3189b369 /tests/zfs-tests/include
parent19229e5f1ee3b7ff4709e63f64c559518f2fd82e (diff)
ZTS: switch to rsync for directory diffs
While "diff -r" is the most straightforward way of comparing directory trees for differences, it has two major issues: * File metadata is not compared, which means that subtle bugs may be missed even if a test is written that exercises the buggy behaviour. * diff(1) doesn't know how to compare special files -- it assumes they are always different, which means that a test using diff(1) on special files will always fail (resulting in such tests not being added). rsync can be used in a very similar manner to diff (with the -ni flags), but has the additional benefit of being able to detect and resolve many more differences between directory trees. In addition, rsync has a standard set of features and flags while diffs feature set depends on whether you're using GNU or BSD binutils. Note that for several of the test cases we expect that file timestamps will not match. For example, the ctime for a file creation or modify event is stored in the intent log but not the mtime. Thus when replaying the log the correct ctime is set but the current mtime is used. This is the expected behavior, so to prevent these tests from failing, there's a replay_directory_diff function which ignores those kinds of changes. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Aleksa Sarai <[email protected]> Closes #12588
Diffstat (limited to 'tests/zfs-tests/include')
-rw-r--r--tests/zfs-tests/include/commands.cfg1
-rw-r--r--tests/zfs-tests/include/libtest.shlib83
2 files changed, 84 insertions, 0 deletions
diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg
index 51052e801..fa3d12d66 100644
--- a/tests/zfs-tests/include/commands.cfg
+++ b/tests/zfs-tests/include/commands.cfg
@@ -77,6 +77,7 @@ export SYSTEM_FILES_COMMON='arp
readlink
rm
rmdir
+ rsync
scp
script
sed
diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib
index b229a1615..bb8cb5c8c 100644
--- a/tests/zfs-tests/include/libtest.shlib
+++ b/tests/zfs-tests/include/libtest.shlib
@@ -4303,3 +4303,86 @@ function wait_for_children #children
done
return $rv
}
+
+#
+# Compare two directory trees recursively in a manner similar to diff(1), but
+# using rsync. If there are any discrepancies, a summary of the differences are
+# output and a non-zero error is returned.
+#
+# If you're comparing a directory after a ZIL replay, you should set
+# LIBTEST_DIFF_ZIL_REPLAY=1 or use replay_directory_diff which will cause
+# directory_diff to ignore mtime changes (the ZIL replay won't fix up mtime
+# information).
+#
+function directory_diff # dir_a dir_b
+{
+ dir_a="$1"
+ dir_b="$2"
+ zil_replay="${LIBTEST_DIFF_ZIL_REPLAY:-0}"
+
+ # If one of the directories doesn't exist, return 2. This is to match the
+ # semantics of diff.
+ if ! [ -d "$dir_a" -a -d "$dir_b" ]; then
+ return 2
+ fi
+
+ # Run rsync with --dry-run --itemize-changes to get something akin to diff
+ # output, but rsync is far more thorough in detecting differences (diff
+ # doesn't compare file metadata, and cannot handle special files).
+ #
+ # Also make sure to filter out non-user.* xattrs when comparing. On
+ # SELinux-enabled systems the copied tree will probably have different
+ # SELinux labels.
+ args=("-nicaAHX" '--filter=-x! user.*' "--delete")
+
+ # NOTE: Quite a few rsync builds do not support --crtimes which would be
+ # necessary to verify that creation times are being maintained properly.
+ # Unfortunately because of this we cannot use it unconditionally but we can
+ # check if this rsync build supports it and use it then. This check is
+ # based on the same check in the rsync test suite (testsuite/crtimes.test).
+ #
+ # We check ctimes even with zil_replay=1 because the ZIL does store
+ # creation times and we should make sure they match (if the creation times
+ # do not match there is a "c" entry in one of the columns).
+ if ( rsync --version | grep -q "[, ] crtimes" >/dev/null ); then
+ args+=("--crtimes")
+ else
+ echo "NOTE: This rsync package does not support --crtimes (-N)."
+ fi
+
+ # If we are testing a ZIL replay, we need to ignore timestamp changes.
+ # Unfortunately --no-times doesn't do what we want -- it will still tell
+ # you if the timestamps don't match but rsync will set the timestamps to
+ # the current time (leading to an itemised change entry). It's simpler to
+ # just filter out those lines.
+ if [ "$zil_replay" -eq 0 ]; then
+ filter=("cat")
+ else
+ # Different rsync versions have different numbers of columns. So just
+ # require that aside from the first two, all other columns must be
+ # blank (literal ".") or a timestamp field ("[tT]").
+ filter=("grep" "-v" '^\..[.Tt]\+ ')
+ fi
+
+ diff="$(rsync "${args[@]}" "$dir_a/" "$dir_b/" | "${filter[@]}")"
+ rv=0
+ if [ -n "$diff" ]; then
+ echo "$diff"
+ rv=1
+ fi
+ return $rv
+}
+
+#
+# Compare two directory trees recursively, without checking whether the mtimes
+# match (creation times will be checked if the available rsync binary supports
+# it). This is necessary for ZIL replay checks (because the ZIL does not
+# contain mtimes and thus after a ZIL replay, mtimes won't match).
+#
+# This is shorthand for LIBTEST_DIFF_ZIL_REPLAY=1 directory_diff <...>.
+#
+function replay_directory_diff # dir_a dir_b
+{
+ LIBTEST_DIFF_ZIL_REPLAY=1 directory_diff "$@"
+ return $?
+}