tests: exercise more of the new FIEMAP copying code
authorJim Meyering <meyering@redhat.com>
Fri, 21 May 2010 16:28:42 +0000 (18:28 +0200)
committerJim Meyering <meyering@redhat.com>
Sun, 30 Jan 2011 19:44:10 +0000 (20:44 +0100)
* tests/cp/sparse-fiemap: Ensure that a file with many extents
(more than fit in copy.c's internal buffer) is copied properly.
Don't require root access if current partition is btrfs or xfs.
Use init.sh, not test-lib.sh.
* tests/filefrag-extent-compare: New file.

tests/cp/sparse-fiemap
tests/filefrag-extent-compare [new file with mode: 0644]

index 21b02ac..dc0cf60 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 # Test cp --sparse=always through fiemap copy
 
-# Copyright (C) 2006-2010 Free Software Foundation, Inc.
+# Copyright (C) 2010 Free Software Foundation, Inc.
 
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -21,29 +21,34 @@ if test "$VERBOSE" = yes; then
   cp --version
 fi
 
-. $srcdir/test-lib.sh
-require_root_
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
 
-cwd=`pwd`
-cleanup_() { cd /; umount "$cwd/mnt"; }
+if df -T -t btrfs -t xfs -t ext4 -t ocfs2 . ; then
+  : # Current dir is on a partition with working extents.  Good!
+else
+  # It's not;  we need to create one, hence we need root access.
+  require_root_
 
-skip=0
-# Create an ext4 loopback file system
-dd if=/dev/zero of=blob bs=8192 count=1000 || skip=1
-mkdir mnt
-mkfs -t ext4 -F blob ||
-  skip_test_ "failed to create ext4 file system"
-mount -oloop blob mnt   || skip=1
-cd mnt                  || skip=1
-echo test > f           || skip=1
-test -s f               || skip=1
+  cwd=$PWD
+  cleanup_() { cd /; umount "$cwd/mnt"; }
 
-test $skip = 1 &&
-  skip_test_ "insufficient mount/ext4 support"
+  skip=0
+  # Create an ext4 loopback file system
+  dd if=/dev/zero of=blob bs=32k count=1000 || skip=1
+  mkdir mnt
+  mkfs -t ext4 -F blob ||
+    skip_test_ "failed to create ext4 file system"
+  mount -oloop blob mnt   || skip=1
+  cd mnt                  || skip=1
+  echo test > f           || skip=1
+  test -s f               || skip=1
 
-# Create a 1TiB sparse file
-dd if=/dev/zero of=mnt/sparse bs=1k count=1 seek=1G || framework_failure
+  test $skip = 1 &&
+    skip_test_ "insufficient mount/ext4 support"
+fi
 
+# Create a 1TiB sparse file
+dd if=/dev/zero of=sparse bs=1k count=1 seek=1G || framework_failure
 
 # It takes many minutes to copy this sparse file using the old method.
 # By contrast, it takes far less than 1 second using FIEMAP-copy.
@@ -53,4 +58,61 @@ timeout 10 cp --sparse=always sparse fiemap || fail=1
 # in bytes as the original.
 test $(stat --printf %s sparse) = $(stat --printf %s fiemap) || fail=1
 
+# =================================================
+# Ensure that we exercise the FIEMAP-copying code enough
+# to provoke at least two iterations of the do...while loop
+# in which it calls ioctl (fd, FS_IOC_FIEMAP,...
+# This also verifies that non-trivial extents are preserved.
+
+$PERL -e 1 || skip_test_ 'skipping part of this test; you lack perl'
+
+# Extract logical block number and length pairs from filefrag -v output.
+# The initial sed is to remove the "eof" from the normally-empty "flags" field.
+# That is required when that final extent has no number in the "expected" field.
+f()
+{
+  sed 's/ eof$//' $@ \
+    | awk '/^ *[0-9]/ {printf "%d %d ", $2 ,NF < 5 ? $NF : $5 } END {print ""}'
+}
+
+for i in $(seq 1 2 21); do
+  for j in 1 2 31 100; do
+    $PERL -e 'BEGIN { $n = '$i' * 1024; *F = *STDOUT }' \
+          -e 'for (1..'$j') { sysseek (*F, $n, 1)' \
+          -e '&& syswrite (*F, chr($_)x$n) or die "$!"}' > j1 || fail=1
+    # sync
+    cp --sparse=always j1 j2 || fail=1
+    # sync
+    # Technically we may need the 'sync' uses above, but
+    # uncommenting them makes this test take much longer.
+
+    cmp j1 j2 || fail=1
+    filefrag -v j1 | grep extent \
+      || skip_test_ 'skipping part of this test; you lack filefrag'
+
+    # Here is sample filefrag output:
+    #   $ perl -e 'BEGIN{$n=16*1024; *F=*STDOUT}' \
+    #          -e 'for (1..5) { sysseek(*F,$n,1)' \
+    #          -e '&& syswrite *F,"."x$n or die "$!"}' > j
+    #   $ filefrag -v j
+    #   File system type is: ef53
+    #   File size of j is 163840 (40 blocks, blocksize 4096)
+    #    ext logical physical expected length flags
+    #      0       4  6258884               4
+    #      1      12  6258892  6258887      4
+    #      2      20  6258900  6258895      4
+    #      3      28  6258908  6258903      4
+    #      4      36  6258916  6258911      4 eof
+    #   j: 6 extents found
+
+    # exclude the physical block numbers; they always differ
+    filefrag -v j1 > ff1 || fail=1
+    filefrag -v j2 > ff2 || fail=1
+    { f ff1; f ff2; } \
+      | $PERL $abs_top_srcdir/tests/filefrag-extent-compare \
+        || { fail=1; break; }
+  done
+  test $fail = 1 && break
+done
+
 Exit $fail
diff --git a/tests/filefrag-extent-compare b/tests/filefrag-extent-compare
new file mode 100644 (file)
index 0000000..3c095d5
--- /dev/null
@@ -0,0 +1,68 @@
+eval '(exit $?0)' && eval 'exec perl -wS "$0" ${1+"$@"}'
+  & eval 'exec perl -wS "$0" $argv:q'
+    if 0;
+# Determine whether two files have the same extents by comparing
+# the logical block numbers and lengths from filefrag -v for each.
+
+# Invoke like this:
+# This helper function, f, extracts logical block number and lengths.
+# f() { awk '/^ *[0-9]/ {printf "%d %d ",$2,NF<5?$NF:$5} END {print ""}'; }
+# { filefrag -v j1 | f; filefrag -v j2 | f; } | ./filefrag-extent-compare
+
+use warnings;
+use strict;
+(my $ME = $0) =~ s|.*/||;
+
+my @line = <>;
+my $n_lines = @line;
+$n_lines == 2
+  or die "$ME: expected exactly two input lines; got $n_lines\n";
+
+my @A = split ' ', $line[0];
+my @B = split ' ', $line[1];
+@A % 2 || @B % 2
+  and die "$ME: unexpected input: odd number of numbers; expected even\n";
+
+my @a;
+my @b;
+foreach my $i (0..@A/2-1) { $a[$i] = { L_BLK => $A[2*$i], LEN => $A[2*$i+1] } };
+foreach my $i (0..@B/2-1) { $b[$i] = { L_BLK => $B[2*$i], LEN => $B[2*$i+1] } };
+
+my $i = 0;
+my $j = 0;
+while (1)
+  {
+    !defined $a[$i] && !defined $b[$j]
+      and exit 0;
+    defined $a[$i] && defined $b[$j]
+      or die "\@a and \@b have different lengths, even after adjustment\n";
+    ($a[$i]->{L_BLK} == $b[$j]->{L_BLK}
+     && $a[$i]->{LEN} == $b[$j]->{LEN})
+      and next;
+    ($a[$i]->{LEN} < $b[$j]->{LEN}
+     && exists $a[$i+1] && $a[$i]->{LEN} + $a[$i+1]->{LEN} == $b[$j]->{LEN})
+      and ++$i, next;
+    exists $b[$j+1] && $a[$i]->{LEN} == $b[$i]->{LEN} + $b[$i+1]->{LEN}
+      and ++$j, next;
+    die "differing extent:\n"
+      . "  [$i]=$a[$i]->{L_BLK} $a[$i]->{LEN}\n"
+      . "  [$j]=$b[$j]->{L_BLK} $b[$j]->{LEN}\n"
+  }
+continue
+  {
+    ++$i;
+    ++$j;
+  }
+
+### Setup "GNU" style for perl-mode and cperl-mode.
+## Local Variables:
+## mode: perl
+## perl-indent-level: 2
+## perl-continued-statement-offset: 2
+## perl-continued-brace-offset: 0
+## perl-brace-offset: 0
+## perl-brace-imaginary-offset: 0
+## perl-label-offset: -2
+## perl-extra-newline-before-brace: t
+## perl-merge-trailing-else: nil
+## End: