From 6d5c85576cb7ed198952f79fc908b10d775170fe Mon Sep 17 00:00:00 2001
From: Matteo Croce <teknoraver@meta.com>
Date: Wed, 2 Oct 2024 00:04:01 +0200
Subject: [PATCH 4/4] reflink: add unit tests

Add two tests for the --reflink option.

reflink.at:
- creates an archive with --reflink, with member sizes and a deep
  member name chosen to exercise the pad record sizing
- checks that the archive contains one comment pad record per
  non-empty regular member
- extracts the archive with --reflink and verifies the contents
  byte for byte, also to stdout and from a non-aligned archive,
  which must fall back to a regular copy

reflink01.at:
- on a filesystem supporting copy-on-write clones, verifies with
  filefrag that the extracted members really share extents with the
  archive; skipped elsewhere
- with a small record size, verifies that the last member, whose
  rounded-up range would poke past the archive end, is cloned up to
  the archive EOF and still shares its extents

* tests/reflink.at: New file.
* tests/reflink01.at: New file.
* tests/Makefile.am (TESTSUITE_AT): Add them.
* tests/testsuite.at: Include them.
---
 tests/Makefile.am  |  2 ++
 tests/reflink.at   | 80 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/reflink01.at | 73 ++++++++++++++++++++++++++++++++++++++++++
 tests/testsuite.at |  4 +++
 4 files changed, 159 insertions(+)
 create mode 100644 tests/reflink.at
 create mode 100644 tests/reflink01.at

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4f666b09..843c6fbd 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -216,6 +216,8 @@ TESTSUITE_AT = \
  positional03.at\
  recurs02.at\
  recurse.at\
+ reflink.at\
+ reflink01.at\
  remfiles01.at\
  remfiles02.at\
  remfiles03.at\
diff --git a/tests/reflink.at b/tests/reflink.at
new file mode 100644
index 00000000..e21b67b2
--- /dev/null
+++ b/tests/reflink.at
@@ -0,0 +1,80 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+# Test suite for GNU tar.
+# Copyright 2013-2025 Free Software Foundation, Inc.
+#
+# GNU tar is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# GNU tar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Description: ensure tar can create archives with the data alignment
+# padding used by reflinks, due option --reflink, and extract them
+# correctly both with and without reflinks.
+#
+
+AT_SETUP([reflink archive])
+AT_KEYWORDS([create extract reflink archive])
+
+AT_TAR_CHECK([
+mkdir dir
+: > dir/0k
+genfile --length 1 --file dir/1b
+genfile --length 3070 --file dir/3k
+genfile --length 4096 --file dir/4k
+genfile --length 5000 --file dir/5k
+genfile --length 9216 --file dir/9k
+
+# A deep member name grows the extended header past the size of the
+# pad record, exercising the comment sizing logic.
+d=@S|@(awk 'BEGIN { s = "dir"
+  for (i = 0; i < 50; i++)
+    s = s "/looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong" i
+  print s }')
+mkdir -p @S|@d || exit 77
+genfile --length 2000 --file @S|@d/deep
+
+tar -cf foo.tar --reflink --sort=name dir
+
+# each non-empty regular member carries a comment pad record
+LC_ALL=C tr -c '@<:@:print:@:>@' '@<:@\n*@:>@' < foo.tar |
+  LC_ALL=C grep -c ' comment='
+
+# extract using reflinks and verify contents
+mkdir out
+(cd out && tar -xf ../foo.tar --reflink)
+cmp dir/1b out/dir/1b
+cmp dir/3k out/dir/3k
+cmp dir/4k out/dir/4k
+cmp dir/5k out/dir/5k
+cmp dir/9k out/dir/9k
+cmp @S|@d/deep out/@S|@d/deep
+echo reflink-ok
+
+# extraction to stdout must not use reflinks
+tar -xOf foo.tar --reflink dir/3k dir/5k > cat.out
+cat dir/3k dir/5k | cmp - cat.out
+echo stdout-ok
+
+# extracting a non-aligned archive falls back to a regular copy
+tar -cf plain.tar dir
+mkdir out2
+(cd out2 && tar -xf ../plain.tar --reflink)
+cmp dir/9k out2/dir/9k
+echo fallback-ok
+],
+[0],
+[6
+reflink-ok
+stdout-ok
+fallback-ok
+],[],[],[],[pax])
+
+AT_CLEANUP
diff --git a/tests/reflink01.at b/tests/reflink01.at
new file mode 100644
index 00000000..68735d1e
--- /dev/null
+++ b/tests/reflink01.at
@@ -0,0 +1,73 @@
+# Process this file with autom4te to create testsuite. -*- Autotest -*-
+# Test suite for GNU tar.
+# Copyright 2013-2025 Free Software Foundation, Inc.
+#
+# GNU tar is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# GNU tar is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Description: on a filesystem supporting copy-on-write clones, ensure
+# that extracting with --reflink really shares blocks with the archive
+# instead of silently falling back to a copy.  This catches alignment
+# regressions on the create side, which would otherwise only show up
+# as a performance loss.  The test is skipped when the filesystem or
+# the required tools do not support reflinks.
+#
+
+AT_SETUP([reflink clone sharing])
+AT_KEYWORDS([create extract reflink reflink01])
+
+AT_TAR_CHECK([
+# require a filesystem with working reflinks and a filefrag that
+# reports shared extents
+genfile --length 100000 --file probe.src
+cp --reflink=always probe.src probe 2>/dev/null || exit 77
+sync
+filefrag -v probe 2>/dev/null | grep -qi shared || exit 77
+
+mkdir dir
+genfile --length 50000 --file dir/a
+genfile --length 4096 --file dir/b
+genfile --length 5001 --file dir/c
+
+tar -cf foo.tar --reflink --sort=name dir
+
+mkdir out
+(cd out && tar -xf ../foo.tar --reflink)
+cmp dir/a out/dir/a
+cmp dir/b out/dir/b
+cmp dir/c out/dir/c
+
+# every extracted member must share extents with the archive
+sync
+filefrag -v out/dir/a | grep -qi shared || echo a not shared
+filefrag -v out/dir/b | grep -qi shared || echo b not shared
+filefrag -v out/dir/c | grep -qi shared || echo c not shared
+echo shared-ok
+
+# with a small record size the rounded-up range of the last member
+# pokes past the end of the archive: it must be cloned up to the
+# archive EOF and still share its extents
+tar -cf small.tar --reflink --sort=name -b 1 dir
+mkdir out2
+(cd out2 && tar -xf ../small.tar --reflink)
+cmp dir/c out2/dir/c
+sync
+filefrag -v out2/dir/c | grep -qi shared || echo tail not shared
+echo eof-clone-ok
+],
+[0],
+[shared-ok
+eof-clone-ok
+],[],[],[],[pax])
+
+AT_CLEANUP
diff --git a/tests/testsuite.at b/tests/testsuite.at
index ab30c2ab..2df1aeea 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -548,3 +548,7 @@ m4_include([star/ustar-big-2g.at])
 m4_include([star/ustar-big-8g.at])
 
 m4_include([star/pax-big-10g.at])
+
+AT_BANNER([Reflinks])
+m4_include([reflink.at])
+m4_include([reflink01.at])
-- 
2.50.1

