diff mbox series

[f2fs-dev,RFC,08/24] tests: prepare helper scripts for testcases

Message ID 20241029120956.4186731-9-shengyong@oppo.com (mailing list archive)
State New
Headers show
Series f2fs-tools: add testcases | expand

Commit Message

Sheng Yong Oct. 29, 2024, 12:09 p.m. UTC
This patch addes helper scripts for auto testcases, and testcases of
fsck will be submitted in the following commits.

The basic idea of these testcases are:
 1. create f2fs image
 2. corrupt the image by inject specific fields
 3. fsck fixes the corrupted image
 4. verify fsck output with expected message

The helper scripts include:
 * test_config.in: is used to generate the basic configurations of all
                   testcases.
 * runtests.in: is used to generate `runtests'.
 * filter.sed: is used to remove unnecessary messages and cleanup
               arbitrary values.
 * helpers: provides helper functions

The usage of runtests:
 * run all testcases:
     runtests
 * run one testcase:
     runtests <testcase directory path>
 * cleanup previous results:
     runtests clean

Each testcase should have a sub-directory, where three files should be
included:
 * README: describe information of the testcase
 * script: testcase itself
 * expect.in: expected output message
an optional file is:
 * img.tar.gz: if some scenario is too complex to create by script, the
               gz file could be prepared in advance, and the operations
               of how to create the image should be described in README

New files are generated in the testcase directory after test:
 * log: output in detail
 * expect: derived from expect.in
 * out: output that will be compared with expect
 * PASS: testcase is passed
 * FAIL: testcase is failed

Signed-off-by: Sheng Yong <shengyong@oppo.com>
---
 .gitignore           |  13 ++++
 Makefile.am          |   2 +-
 configure.ac         |   1 +
 tests/Makefile.am    |  28 ++++++++
 tests/filter.sed     |  60 +++++++++++++++++
 tests/helpers        | 157 +++++++++++++++++++++++++++++++++++++++++++
 tests/runtests.in    |  46 +++++++++++++
 tests/test_config.in |  47 +++++++++++++
 8 files changed, 353 insertions(+), 1 deletion(-)
 create mode 100644 tests/Makefile.am
 create mode 100644 tests/filter.sed
 create mode 100644 tests/helpers
 create mode 100644 tests/runtests.in
 create mode 100644 tests/test_config.in
diff mbox series

Patch

diff --git a/.gitignore b/.gitignore
index 49809446793d..c824e8f8091f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,8 @@  stamp-h1
 
 /mkfs/mkfs.f2fs
 /fsck/fsck.f2fs
+/fsck/dump.f2fs
+/fsck/inject.f2fs
 /tools/fibmap.f2fs
 /tools/parse.f2fs
 /tools/f2fscrypt
@@ -54,3 +56,14 @@  stamp-h1
 # cscope files
 cscope.*
 ncscope.*
+
+# testcase files
+tests/*/log
+tests/*/out
+tests/*/PASS
+tests/*/FAIL
+tests/*/expect
+tests/meta.img
+tests/data.img
+tests/runtests
+tests/test_config
diff --git a/Makefile.am b/Makefile.am
index d2921d626e48..0c9ec66d02b6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,4 +2,4 @@ 
 
 ACLOCAL_AMFLAGS = -I m4
 
-SUBDIRS = man lib mkfs fsck tools
+SUBDIRS = man lib mkfs fsck tools tests
diff --git a/configure.ac b/configure.ac
index 2053a65bbb36..364dd162e7c0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -263,6 +263,7 @@  AC_CONFIG_FILES([
 	fsck/Makefile
 	tools/Makefile
 	tools/f2fs_io/Makefile
+	tests/Makefile
 ])
 
 AC_CHECK_MEMBER([struct blk_zone.capacity],
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 000000000000..670cd373c628
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,28 @@ 
+## Makefile.am
+
+noinst_SCRIPTS = test_config runtests
+CLEANFILES = $(bin_SCRIPTS)
+EXTRA_DIST = test_config.in runtests.in
+
+test_config: $(srcdir)/test_config.in
+	@echo "Creating test_config script..."
+	@[ -f test_config ] && chmod u+w test_config || true
+	@echo "#!/bin/bash" > test_config
+	@echo "" >> test_config
+	@echo "TOPDIR=@top_srcdir@" >> test_config
+	@cat $(srcdir)/test_config.in >> test_config
+	@chmod +x-w test_config
+
+runtests: $(srcdir)/runtests.in
+	@echo "Creating runtests script..."
+	@[ -f runtests ] && chmod u+w runtests || true
+	@echo "#!/bin/bash" > runtests
+	@echo "" >> runtests
+	@echo "SRCDIR=@srcdir@" >> runtests
+	@cat $(srcdir)/runtests.in >> runtests
+	@chmod +x-w runtests
+
+clean-local:
+	@[ -f runtests ] && runtests clean || true
+	@rm -f runtests test_config
+
diff --git a/tests/filter.sed b/tests/filter.sed
new file mode 100644
index 000000000000..ea69c8eb65a4
--- /dev/null
+++ b/tests/filter.sed
@@ -0,0 +1,60 @@ 
+## f2fs info that could be ignored
+
+/^Info: No Kernel Check/d
+/^Info: not exist \/proc\/version!/d
+/^Info: version timestamp/d
+/be careful to overwrite a mounted loopback file/d
+/^Info: MKFS version/,+1d
+/^Info: FSCK version/,+2d
+/^Info: superblock encrypt level/d
+/^Info: total FS sectors/d
+/^Done: [0-9\.]\+ secs/d
+/^Info: total FS sectors/d
+/^\[FSCK\] Max image size/d
+/^Info: flush_journal_entries/d
+/^Info: Segments per section =/d
+/^Info: Sections per zone =/d
+/^Info: Device\[[0-9]\]/d
+/^Info: CKPT version =/d
+/^Info: superblock features =/d
+/^Info: flush_journal_entries/d
+/^Info: Corrupted valid nat_bits in checkpoint/d
+/^Info: Checked valid nat_bits in checkpoint/d
+
+## fsck messages should be kept, but some numeric is arbitrary and should
+## be removed
+
+# e.g [ASSERT] (is_valid_ssa_data_blk: 340) => [ASSERT] (is_valid_ssa_data_blk: x)
+s/\(\[ASSERT\] ([a-zA-Z_][0-9a-zA-Z_]\+:\s*\)[0-9]\+)/\1x)/g
+# e.g [FIX] (nullify_nat_entry:3274) => [FIX] (nullify_nat_entry:x)
+s/\(\[FIX\] ([a-zA-Z_][0-9a-zA-Z_]\+:\s*\)[0-9]\+)/\1x)/g
+# e.g [fsck_chk_quota_files:2242] => [fsck_chk_quota_files:x]
+s/^\(\[[a-zA-Z_][0-9a-zA-Z_]\+:\s*\)[0-9]\+\]/\1x\]/g
+# e.g Info: Duplicate valid checkpoint to mirror position 270848 -> 270336 =>
+#     Info: Duplicate valid checkpoint to mirror position x -> x
+s/\(Info: Duplicate valid checkpoint to mirror position \)[0-9]\+\( -> \)[0-9]\+/\1x\2x/g
+# e.g Info: checkpoint state = 1c5 :  trimmed nat_bits crc compacted_summary unmount =>
+#     Info: checkpoint state = x :  nat_bits crc compacted_summary unmount
+s/\(Info: checkpoint state = \)[0-9a-f]\+ :/\1x :/g
+s/\(Info: checkpoint state = x :.*\) trimmed\(.*$\)/\1\2/g
+# e.g Info: write_checkpoint() cur_cp:1 => Info: write_checkpoint() cur_cp:x
+s/\(Info: write_checkpoint() cur_cp:\)[0-1]/\1x/g
+# e.g Info: fix_checkpoint() cur_cp:1 => Info: fix_checkpoint() cur_cp:x
+s/\(Info: fix_checkpoint() cur_cp:\)[0-1]/\1x/g
+# e.g [FSCK] Unreachable nat entries                        [Fail] [0x1] =>
+#     [FSCK] Unreachable nat entries                        [Fail] [x]
+# e.g [FSCK] Unreachable nat entries                        [Ok...] [0x0] =>
+#     [FSCK] Unreachable nat entries                        [Ok...] [x] =>
+s/\(\[FSCK\] Unreachable nat entries\s*\[\(Ok\.\.\|Fail\)\] \[\)0x[0-9a-f]\+\]/\1x\]/g
+s/\(\[FSCK\] Hard link checking for regular file\s*\[\(Ok\.\.\|Fail\)\] \[\)0x[0-9a-f]\+\]/\1x\]/g
+# e.g [FSCK] valid_block_count matching with CP             [Ok..] [0x10] =>
+#     [FSCK] valid_block_count matching with CP             [Ok..] [x]
+# e.g [FSCK] valid_block_count matching with CP             [Fail] [0x10, 0x9] =>
+#     [FSCK] valid_block_count matching with CP             [Fail] [x, x]
+s/\(\[FSCK\] valid_block_count matching with CP\s*\[\(Ok\.\.\|Fail\)\] \[\)[0-9a-fx, ]\+\]/\1x\]/g
+s/\(\[FSCK\] valid_node_count matching with CP (de lookup)\s*\[\(Ok\.\.\|Fail\)\] \[\)[0-9a-fx, ]\+\]/\1x\]/g
+s/\(\[FSCK\] valid_node_count matching with CP (nat lookup)\s*\[\(Ok\.\.\|Fail\)\] \[\)[0-9a-fx, ]\+\]/\1x\]/g
+s/\(\[FSCK\] valid_inode_count matched with CP\s*\[\(Ok\.\.\|Fail\)\] \[\)[0-9a-fx, ]\+\]/\1x\]/g
+s/\(\[FSCK\] free segment_count matched with CP\s*\[\(Ok\.\.\|Fail\)\] \[\)[0-9a-fx, ]\+\]/\1x\]/g
+
+## inject info is verifed by fsck result, so it also could be removed
diff --git a/tests/helpers b/tests/helpers
new file mode 100644
index 000000000000..312ecb9c8e99
--- /dev/null
+++ b/tests/helpers
@@ -0,0 +1,157 @@ 
+#!/bin/sh
+
+# If multiple devices are used, META is the first device and DATA is the
+# second device. If there is only one device, META is used only.
+META=`realpath $TOPDIR/tests/meta.img`
+DATA=`realpath $TOPDIR/tests/data.img`
+OUT=$TESTDIR/out
+EXP=$TESTDIR/expect
+LOG=$TESTDIR/log
+
+# $1: pre: cleanup previous test result
+#     post: remove image files
+cleanup() {
+	if [ x"$1" = x"pre" ]; then
+		rm $TESTDIR/PASS $TESTDIR/FAIL $TESTDIR/expect $OUT $LOG
+	fi
+
+	if [ x"$1" = x"pre" ] || [ x"$1" = x"post" ]; then
+		echo "rm $META $DATA"
+	fi
+}
+
+# check test result
+check_result() {
+	local NAME=`basename $TESTDIR`
+
+	if [ ! -e $EXP ]; then
+		cp "$EXP".in $EXP
+	fi
+	sed -f $FILTER -i $OUT
+	cmp -s $OUT $EXP
+	if [ x"$?" = x"0" ]; then
+		echo "$NAME: $DESC: pass"
+		touch $TESTDIR/PASS
+	else
+		echo "$NAME: $DESC: failed"
+		echo "diff $EXP $OUT"
+		diff $EXP $OUT > $TESTDIR/FAIL
+		exit
+	fi
+}
+
+# $1: path of image file
+# $2: size in MB
+create_img_file() {
+	truncate -s $2"M" $1
+}
+
+make_f2fs() {
+	local metasize=10
+	local datasize=118
+
+	if [ $SEGS_PER_SEC -gt 1 ]; then
+		metasize=$((3 * ($SEGS_PER_SEC * 2)))
+		datasize=$((11* ($SEGS_PER_SEC * 2)))
+		MKFS_OPTS="$MKFS_OPTS -s $SEGS_PER_SEC"
+	fi
+	if [ $MULTIDEV -eq 1 ]; then
+		create_img_file $META $metasize
+		create_img_file $DATA $datasize
+		MKFS_OPTS="$MKFS_OPTS -c $DATA"
+	else
+		create_img_file $META $(($metasize + $datasize))
+	fi
+
+	$MKFS $MKFS_OPTS $META
+	$DUMP -d 1 $META
+
+	return $?
+}
+
+safe_mount() {
+	local dev=""
+
+	# If multiple devices are used, DATA should be associated to a
+	# loop device in advance.
+	# dump/fsck will check the path of the the DATA device, to make
+	# them happy, replace devs[1].path with the loop device before
+	# mount, and restore it after umount.
+	if [ $MULTIDEV -eq 1 ]; then
+		dev=`losetup -f`
+		losetup $dev $DATA
+		if [ $? -ne 0 ]; then
+			echo "cannot setup loop dev: losetup $dev $DATA"
+			return -1
+		fi
+		$INJECT --sb 1 --mb devs.path --idx 1 --str $dev $META
+	fi
+
+	mount -t f2fs $*
+	return $?
+}
+
+#1: mntpoint
+safe_umount() {
+	local max_retry=10
+	local dev=""
+	local i=0
+
+	umount $1
+	while [ $? -ne 0 ]; do
+		i=$(($i + 1))
+		if [ $i -gt $max_retry ]; then
+			return 1
+		fi
+		echo "cannot umount f2fs image, retry: $i"
+		sleep 1
+		umount $1
+	done
+
+	dev=`losetup -j $DATA | cut -d ":" -f 1`
+	if [ x"$dev" != x"" ]; then
+		losetup -d $dev
+		# restore devs[1].path from $dev to $DATA
+		$INJECT --sb 1 --mb devs.path --idx 1 --str $DATA $META
+	fi
+	return 0
+}
+
+# $1: member of sb
+get_sb() {
+	local sb_cp="`$DUMP $DUMP_OPT -d 1 $META`"
+	local cut_here=`echo "$sb_cp" | awk '/^\| Checkpoint/ {print NR}'`
+	local sb="`echo "$sb_cp" | head -n $(($cut_here - 2))`"
+	local val=`echo "$sb" | grep "^$1\s" | sed "s/^$1\s*\[0x\s*[0-9a-f]\+ : \([0-9]*\)\]/\1/g"`
+	echo $val
+}
+
+# $1: member of cp
+get_cp() {
+	local sb_cp="`$DUMP $DUMP_OPT -d 1 $META`"
+	local cut_here=`echo "$sb_cp" | awk '/^\| Checkpoint/ {print NR}'`
+	local cp="`echo "$sb_cp" | tail -n +$(($cut_here - 2))`"
+	local val=`echo "$cp" | grep "^$1\s" | sed "s/^$1\s*\[0x\s*[0-9a-f]\+ : \([0-9]*\)\]/\1/g"`
+	echo $val
+}
+
+# $1: blkaddr
+get_segno() {
+	local main_blkaddr=`get_sb main_blkaddr`
+	local log_blks_per_seg=`get_sb log_blocks_per_seg`
+	echo $((($1 - $main_blkaddr) >> $log_blks_per_seg))
+}
+
+# $1: segno
+start_block() {
+	local main_blkaddr=`get_sb main_blkaddr`
+	local log_blks_per_seg=`get_sb log_blocks_per_seg`
+	echo $(($main_blkaddr + ($1 << $log_blks_per_seg)))
+}
+
+# $1: blkaddr
+offset_in_seg() {
+	local main_blkaddr=`get_sb main_blkaddr`
+	local log_blks_per_seg=`get_sb log_blocks_per_seg`
+	echo $((($1 - $main_blkaddr) % (1 << $log_blks_per_seg)))
+}
diff --git a/tests/runtests.in b/tests/runtests.in
new file mode 100644
index 000000000000..d49634752d42
--- /dev/null
+++ b/tests/runtests.in
@@ -0,0 +1,46 @@ 
+#!/bin/sh
+#
+# run testcases
+#
+# run all testcases:
+#   runtests
+#
+# run one testcase:
+#   runtests <testcase directory path>
+#
+# cleanup:
+#   runtests clean
+#
+
+# clean temporary files
+if [ x"$1" = x"clean" ]; then
+	find $SRCDIR \( -name FAIL -o -name PASS -o -name expect -o -name log -o -name out -o -name meta.img -o -name data.img \) -exec rm -f {} \;
+	exit
+fi
+
+TEST_CONFIG=$SRCDIR/test_config
+. $TEST_CONFIG
+
+# $1: path of testcase path
+run_one_test() {
+	local subdir=`realpath $1`
+	local name=`basename $subdir`
+	echo "Run testcase: $name ..."
+	TESTDIR=$subdir
+	. $subdir/script
+	echo ""
+}
+
+# run one testcase
+# $1: testcase path
+if [ x"$1" != x"" ]; then
+	run_one_test $1
+	exit
+fi
+
+# run all testcases
+TESTS=`find ./ -type f -name script`
+for testcase in ${TESTS[@]}; do
+	subdir=`dirname $testcase`
+	run_one_test $subdir
+done
diff --git a/tests/test_config.in b/tests/test_config.in
new file mode 100644
index 000000000000..b4249dc26179
--- /dev/null
+++ b/tests/test_config.in
@@ -0,0 +1,47 @@ 
+#!/bin/sh
+#
+# Test configuration
+#
+
+check_executable() {
+	file $1 | grep ELF > /dev/null
+	[ $? -ne 0 ] && { echo "ERROR: $1 not ELF"; exit; }
+}
+
+check_diff() {
+	diff $1 $2 > /dev/null
+	[ $? -ne 0 ] && { echo "WARNING: $1 and $2 differ"; }
+}
+
+# path of tools
+MKFS="$TOPDIR/mkfs/mkfs.f2fs"
+FSCK="$TOPDIR/fsck/fsck.f2fs"
+DUMP="$TOPDIR/fsck/dump.f2fs"
+INJECT="$TOPDIR/fsck/inject.f2fs"
+F2FS_IO="$TOPDIR/tools/f2fs_io/f2fs_io"
+FILTER="$TOPDIR/tests/filter.sed"
+[ ! -e $MKFS ] && { echo "$MKFS not exist"; exit; }
+[ ! -e $FSCK ] && { echo "$FSCK not exist"; exit; }
+check_executable $FSCK
+[ ! -e $DUMP ] && { cp $FSCK $DUMP; }
+check_executable $DUMP
+check_diff $FSCK $DUMP
+[ ! -e $F2FS_IO ] && { echo "$F2FS_IO not exist"; exit; }
+[ ! -e $INJECT ] && { cp $FSCK $INJECT; }
+check_executable $INJECT
+check_diff $FSCK $INJECT
+
+# config of mkfs
+F2FS_FEATURES="encrypt,extra_attr,compression"
+MKFS_OPTS="-O $F2FS_FEATURES -f"
+MULTIDEV=0
+SEGS_PER_SEC=1
+
+# config of fsck
+FSCK_OPTS="--no-kernel-check -N"
+
+# config of dump
+DUMP_OPTS="-N"
+
+# config of mount
+MNT_OPTS=""