@@ -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
@@ -2,4 +2,4 @@
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = man lib mkfs fsck tools
+SUBDIRS = man lib mkfs fsck tools tests
@@ -263,6 +263,7 @@ AC_CONFIG_FILES([
fsck/Makefile
tools/Makefile
tools/f2fs_io/Makefile
+ tests/Makefile
])
AC_CHECK_MEMBER([struct blk_zone.capacity],
new file mode 100644
@@ -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
+
new file mode 100644
@@ -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
new file mode 100644
@@ -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)))
+}
new file mode 100644
@@ -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
new file mode 100644
@@ -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=""
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