From: Sheng Yong <[email protected]>
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
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
* SKIP: testcase is skipped
Signed-off-by: Sheng Yong <[email protected]>
---
.gitignore | 14 +++
Makefile.am | 2 +-
configure.ac | 1 +
tests/Makefile.am | 26 +++++
tests/filter.sed | 69 +++++++++++
tests/helpers | 269 +++++++++++++++++++++++++++++++++++++++++++
tests/runtests.in | 48 ++++++++
tests/test_config.in | 53 +++++++++
8 files changed, 481 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 --git a/.gitignore b/.gitignore
index 49809446793d..68dbe1c407c5 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,15 @@ stamp-h1
# cscope files
cscope.*
ncscope.*
+
+# testcase files
+tests/*/log
+tests/*/out
+tests/*/PASS
+tests/*/FAIL
+tests/*/SKIP
+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 ddfc3b0f30e2..a819dde93ed1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -265,6 +265,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..1b4dfdc70e44
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,26 @@
+## Makefile.am
+
+noinst_SCRIPTS = test_config runtests
+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 ] && ${SHELL} runtests clean || true
+ @rm -f $(noinst_SCRIPTS)
diff --git a/tests/filter.sed b/tests/filter.sed
new file mode 100644
index 000000000000..b2161a55d454
--- /dev/null
+++ b/tests/filter.sed
@@ -0,0 +1,69 @@
+# output messages that could be removed or ignored
+#
+# Note that some sed only supports POSIX Basic Regular Expression (BRE)
+# syntax. In BRE, metacharacters, for example '|', '\s', '\+', are not
+# supported. So BRE is recommended in this file.
+
+# remove messages which start with "^Info: ", expect for:
+# - Info: Automatic fix mode enabled
+# indicate that fsck -a is used.
+# - Info: Force to fix corruption
+# indicate that fsck -f is used.
+# - Info: No error was reported
+# indicate that fsck does not detect any errors.
+# - Info: checkpoint stop reason
+# - Info: fs errors
+# indicate that fsck detects error recorded in super block.
+/^Info: MKFS version/,+1d
+/^Info: FSCK version/,+2d
+/^Info: Host-managed zoned block device:/,+3d
+/^Info: /{
+ /Info: Automatic fix mode enabled/b out;
+ /Info: Force to fix corruption/b out;
+ /Info: No error was reported/b out;
+ /Info: checkpoint stop reason/b out;
+ /Info: fs errors/b out;
+ d
+}
+# e.g [update_block: 904] Info: Done to update superblock
+/^\[[a-zA-Z_][0-9a-zA-Z_]*:[[:space:]]*[0-9]\{1,\}\] Info: /d
+# e.g [INFO] (fsck_update_sb_flags:2389) --> Casefold: linear_lookup [enable]
+/^\[INFO\]/d
+
+# remove fsck statistics messages
+# e.g [FSCK] valid_block_count matching with CP [Ok..][0xc]
+/^\[FSCK\]/d
+
+# remove trivial messages
+/be careful to overwrite a mounted loopback file/d
+/^Done: [0-9\.]\{1,\} secs/d
+/^update nat(nid:.*) blkaddr \[.*\] in journal/d
+
+# some messages contain arbitrary numeric, ignore them
+# 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_]*:[[:space:]]*\)[0-9]\{1,\})/\1x)/g
+# e.g [FIX] (nullify_nat_entry:3274) => [FIX] (nullify_nat_entry:x)
+s/\(\[FIX\] ([a-zA-Z_][0-9a-zA-Z_]*:[[:space:]]*\)[0-9]\{1,\})/\1x)/g
+# e.g [fsck_chk_quota_files:2242] => [fsck_chk_quota_files:x]
+s/^\(\[[a-zA-Z_][0-9a-zA-Z_]*:[[:space:]]*\)[0-9]\{1,\}\]/\1x\]/g
+
+# exceptions
+# the message is only shown on zoned device, but I don't find a better way
+# to keep this message in expect.in for both zoned and legacy UFS. So remove
+# it for now.
+/Reset write pointer of zone at segment/d
+
+# cut dump messages
+# e.g nid: 7 ino: 7 ver: 0 offset: 0 blkaddr: 28642878 pack:1
cp_ver:0xe42573115ffc7cfc =>
+# nid: 7 ino: 7 ver: 0 offset: 0
+s/\(^nid:.*\) blkaddr:[0-9]\{1,\} pack:[12] cp_ver:0x[0-9a-fA-F]\{1,\}/\1/g
+# e.g segno: 55968 vblocks:512 seg_type:1 mtime:0 sit_pack:2 =>
+# segno: 55968 vblocks:512 seg_type:1 mtime:0
+s/\(^segno:.*\) sit_pack:[12]/\1/g
+
+# keep the following at the end of the file
+:out
+# remove empty lines
+/^$/d
+# remove tail spaces
+s/[[:space:]]\{1,\}$//g
diff --git a/tests/helpers b/tests/helpers
new file mode 100644
index 000000000000..03853f803598
--- /dev/null
+++ b/tests/helpers
@@ -0,0 +1,269 @@
+#!/bin/bash
+
+OUT=$TESTDIR/out
+EXP=$TESTDIR/expect
+LOG=$TESTDIR/log
+
+# $1: string to be trimed
+trim_extra_space() {
+ local line=""
+ while IFS= read -r line; do
+ # trim multiple spaces as one
+ line=`echo "$line" | sed "s/[[:space:]]\{1,\}/ /g"`
+ # delete space in hex values
+ line=`echo "$line" | sed "s/0x /0x/g"`
+ # delete space before and after ":"
+ echo "$line" | sed "s/ *: */:/g"
+ done
+}
+
+# clean up temporary files
+cleanup() {
+ [ -f $TESTDIR/PASS ] && rm $TESTDIR/PASS || true
+ [ -f $TESTDIR/FAIL ] && rm $TESTDIR/FAIL || true
+ [ -f $TESTDIR/SKIP ] && rm $TESTDIR/SKIP || true
+ [ -f $TESTDIR/expect ] && rm $TESTDIR/expect || true
+ [ -f $OUT ] && rm $OUT || true
+ [ -f $LOG ] && rm $LOG || true
+}
+
+# check test result
+check_result() {
+ local NAME=`basename $TESTDIR`
+
+ if [ -e $TESTDIR/SKIP ]; then
+ echo "$NAME: $DESC: skip"
+ return
+ fi
+
+ 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: fail"
+ echo "diff $EXP $OUT"
+ diff $EXP $OUT > $TESTDIR/FAIL
+ exit
+ fi
+}
+
+# $1: dev path
+is_block_dev() {
+ if [ ! -e $1 ]; then
+ return 0
+ fi
+ local dev_real_path=`realpath $1`
+ if [ -b $dev_real_path ]; then
+ # dev is block device
+ return 1
+ fi
+ # dev is not block device
+ return 0
+}
+
+# $1: path of image file
+# $2: size in MB
+create_img_file() {
+ is_block_dev $1
+ if [ $? -eq 1 ]; then
+ # dev is block device, return directly
+ return
+ fi
+ truncate -s $2"M" $1
+}
+
+make_f2fs() {
+ local metasize=10
+ local datasize=118
+ local opts="$MKFS_OPTS"
+
+ if [ $SEGS_PER_SEC -gt 1 ]; then
+ metasize=$((3 * (SEGS_PER_SEC * 2)))
+ datasize=$((11 * (SEGS_PER_SEC * 2)))
+ opts="$MKFS_OPTS -s $SEGS_PER_SEC"
+ fi
+ if [ $MULTIDEV -eq 1 ]; then
+ create_img_file $META $metasize
+ create_img_file $DATA $datasize
+ opts = "$MKFS_OPTS -c $DATA"
+ else
+ create_img_file $META $((metasize + datasize))
+ fi
+
+ $MKFS $opts $META
+ if [ $? -ne 0 ]; then
+ echo "failed to format f2fs" >&2
+ exit
+ fi
+ $DUMP -d 1 $META
+}
+
+safe_mount() {
+ if [ $MULTIDEV -eq 1 ]; then
+ is_block_dev $DATA
+ if [ $? -eq 0 ]; then
+ # If multiple devices are used and DATA is not
+ # a block device, DATA should be associated to
+ # a loop device in advance.
+ # dump/fsck will check the path of the DATA
+ # device, to make them happy, replace devs[1].path
+ # with the loop device before mount, and restore
+ # it after umount.
+ local dev=`losetup -f`
+ losetup $dev $DATA
+ if [ $? -ne 0 ]; then
+ echo "cannot setup loop dev: losetup $dev
$DATA" >&2
+ exit
+ fi
+ $INJECT --sb 1 --mb devs.path --idx 1 --str $dev $META
+ fi
+ fi
+
+ mount -t f2fs $*
+ if [ $? -ne 0 ]; then
+ echo "cannot mount f2fs image" >&2
+ exit
+ fi
+}
+
+# $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
+ echo "cannot umount f2fs image" >&2
+ exit
+ fi
+ echo "cannot umount f2fs image, retry: $i"
+ sleep 1
+ umount $1
+ done
+
+ if [ ! -e $DATA ]; then
+ return
+ fi
+ 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
+}
+
+# $1: subject: cp, sb or node
+# $2: member of subject
+# $3: the number of output lines if there are multiple results, default is 1
+get_mb() {
+ if [ x"$3" = x"" ]; then
+ local nth=1
+ else
+ local nth=$3
+ fi
+ echo "$1" | grep "^$2" | awk "NR==$nth" | trim_extra_space
+}
+
+# $1: subject: cp, sb or node
+# $2: member of subject
+# $3: the number of output lines if there are multiple results, default is 1
+get_mb_val() {
+ if [ x"$3" = x"" ]; then
+ local nth=1
+ else
+ local nth=$3
+ fi
+ local val=`echo "$1" | grep "^$2" | awk "NR==$nth" | \
+ sed "s/^$2[[:space:]]*\[\(.*\)\]$/\1/g"`
+ echo "$val" | grep ":" > /dev/null
+ if [ $? -eq 0 ]; then
+ val=`echo $val | sed "s/.*:[[:space:]]*\([0-9]\{1,\}\)$/\1/g"`
+ fi
+ echo $val
+}
+
+# $1: device
+get_sb() {
+ local sb_cp="`$DUMP $DUMP_OPTS -d 1 $1`"
+ local cut_here=`echo "$sb_cp" | awk '/^\| Checkpoint/ {print NR}'`
+ local sb="`echo "$sb_cp" | head -n $((cut_here - 2))`"
+ echo "$sb"
+}
+
+# $1: device
+get_cp() {
+ local sb_cp="`$DUMP $DUMP_OPTS -d 1 $1`"
+ local cut_here=`echo "$sb_cp" | awk '/^\| Checkpoint/ {print NR}'`
+ local cp="`echo "$sb_cp" | tail -n +$((cut_here - 2))`"
+ echo "$cp"
+}
+
+# $1: blkaddr
+get_segno() {
+ local sb=`get_sb $META`
+ local main_blkaddr=`get_mb_val "$sb" main_blkaddr`
+ local log_blks_per_seg=`get_mb_val "$sb" log_blocks_per_seg`
+ echo $((($1 - main_blkaddr) >> log_blks_per_seg))
+}
+
+# $1: segno
+start_block() {
+ local sb=`get_sb $META`
+ local main_blkaddr=`get_mb_val "$sb" main_blkaddr`
+ local log_blks_per_seg=`get_mb_val "$sb" log_blocks_per_seg`
+ echo $((main_blkaddr + ($1 << log_blks_per_seg)))
+}
+
+# $1: blkaddr
+offset_in_seg() {
+ local sb=`get_sb $META`
+ local main_blkaddr=`get_mb_val "$sb" main_blkaddr`
+ local log_blks_per_seg=`get_mb_val "$sb" log_blocks_per_seg`
+ echo $((($1 - main_blkaddr) % (1 << log_blks_per_seg)))
+}
+
+# reference INIT_FEATURE_TABLE
+# $1: feature
+test_sb_feature() {
+ local sb=`get_sb $META`
+ local feats=`get_mb_val "$sb" feature`
+ case $1 in
+ "encrypt")
+ return $((!!(feats & 0x1)));;
+ "blkzoned")
+ return $((!!(feats & 0x2)));;
+ "extra_attr")
+ return $((!!(feats & 0x8)));;
+ "project_quota")
+ return $((!!(feats & 0x10)));;
+ "inode_checksum")
+ return $((!!(feats & 0x20)));;
+ "flexible_inline_xattr")
+ return $((!!(feats & 0x40)));;
+ "quota")
+ return $((!!(feats & 0x80)));;
+ "inode_crtime")
+ return $((!!(feats & 0x100)));;
+ "lost_found")
+ return $((!!(feats & 0x200)));;
+ "verity")
+ return $((!!(feats & 0x400)));;
+ "sb_checksum")
+ return $((!!(feats & 0x800)));;
+ "casefold")
+ return $((!!(feats & 0x1000)));;
+ "compression")
+ return $((!!(feats & 0x2000)));;
+ "ro")
+ return $((!!(feats & 0x4000)));;
+ esac
+ return 0
+}
diff --git a/tests/runtests.in b/tests/runtests.in
new file mode 100644
index 000000000000..29db79a4a95c
--- /dev/null
+++ b/tests/runtests.in
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# 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 SKIP -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..41ce50052197
--- /dev/null
+++ b/tests/test_config.in
@@ -0,0 +1,53 @@
+#!/bin/bash
+#
+# 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 devices
+# 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`
+
+# 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,quota,project_quota,extra_attr,verity,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=""
--
2.43.0
_______________________________________________
Linux-f2fs-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel