From 76ce1cbb16c2e4041fd19f2f114798a33070af9a Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 28 Apr 2026 11:51:59 +0200 Subject: [PATCH] Add regression check for Btrfs It requires root privileges as it has to create Btrfs filesystems. --- .github/workflows/build.yml | 3 +- Makefile.am | 137 +++++++++++++++++++++++++----------- cmdline/mktest.c | 26 +++---- cmdline/snapraid.c | 7 ++ cmdline/state.h | 1 + cmdline/sync.c | 18 +++-- test/test-btrfs.conf | 9 +++ 7 files changed, 142 insertions(+), 59 deletions(-) create mode 100644 test/test-btrfs.conf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 323e506a..c9019af0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,8 @@ jobs: - uses: actions/checkout@v5 - run: autoreconf -i - run: ./configure --enable-warning-as-error - - run: make all dist check + - run: make all btrfscheck + - run: make distcheck build-win: runs-on: windows-latest defaults: diff --git a/Makefile.am b/Makefile.am index 53a99d99..99da0fd7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -154,6 +154,7 @@ EXTRA_DIST = \ $(LIST_D) \ $(LIST_MAN) \ $(LIST_TXT) \ + test/test-btrfs.conf \ test/test-par1.conf \ test/test-par2.conf \ test/test-par3.conf \ @@ -267,6 +268,9 @@ CHECKFLAGS_VERBOSE = $(CHECKFLAGS_ALPHA) -v --gui # Quiet options CHECKFLAGS = $(CHECKFLAGS_ALPHA) -q -q -q +# Btrfs with no parity limit +CHECKFLAGS_BTRFS = --test-skip-device --test-skip-self --test-io-advise-none --test-force-order-alpha -q -q -q + # Number of files to create if !HAVE_MEMORY_CHECKER if HAVE_POSIX @@ -297,48 +301,101 @@ PARZ = $(srcdir)/test/test-parz.conf PAR4 = $(srcdir)/test/test-par4.conf PAR5 = $(srcdir)/test/test-par5.conf PAR6 = $(srcdir)/test/test-par6.conf +BTRFS = $(srcdir)/test/test-btrfs.conf MSG = @echo ===== SNAPRAID = ./checker$(EXEEXT) -fastcheck: mktest +setupcheck: rm -rf bench rm -f test.log mkdir bench - mkdir bench/disk1 - mkdir bench/disk2 - mkdir bench/disk3 - mkdir bench/disk4 - mkdir bench/disk5 - mkdir bench/disk6 - mkdir bench/pool - $(MSG) Stream - $(TESTENV) $(SNAPRAID) --test-skip-device -H - $(TESTENV) $(SNAPRAID) --test-skip-device -V - $(TESTENV) $(SNAPRAID) test - $(MSG) Create some files - $(TESTENV) ./mktest$(EXEEXT) generate 1 6 $(CHECKCOUNT) $(CHECKSIZE) - echo DUP > bench/disk1/DUP1 - echo DUP > bench/disk1/DUP2 - echo -n > bench/disk1/ZERO - $(MSG) Some commands with a not empty array - $(FAILENV) $(SNAPRAID) $(CHECKFLAGS_COMMON) -c $(CONF) --test-expect-need-sync diff > output.log - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_COMMON) -c $(CONF) sync - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_VERBOSE) -c $(CONF) dup -l test.log - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_VERBOSE) -c $(CONF) list -l test.log > output.log - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_VERBOSE) -c $(CONF) test-rewrite - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_VERBOSE) -c $(CONF) read - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) status -l test.log - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) check - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) -a check - $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) test-dry - $(MSG) Regression test completed with SUCCESS! - $(MSG) Please ignore any error message printed above, they are expected! - $(MSG) Everything OK -check-local: - rm -rf bench - rm -f test.log - mkdir bench +# Not included in main check because it requires root permissions +btrfscheck: mktest checker setupcheck + sudo $(MAKE) btrfscheck_run; \ + status=$$?; \ + sudo $(MAKE) btrfscheck_cleanup; \ + exit $$status + +btrfscheck_run: + mkdir bench/btrfs1 + mkdir bench/btrfs2 + mkdir bench/btrfs3 + $(MSG) Create storages + dd bs=1048576 count=32 if=/dev/zero of=bench/btrfs1.img + dd bs=1048576 count=32 if=/dev/zero of=bench/btrfs2.img + dd bs=1048576 count=32 if=/dev/zero of=bench/btrfs3.img + /sbin/mkfs.btrfs --mixed bench/btrfs1.img + /sbin/mkfs.btrfs --mixed bench/btrfs2.img + /sbin/mkfs.btrfs --mixed bench/btrfs3.img + mount -o loop bench/btrfs1.img bench/btrfs1 + mount -o loop bench/btrfs2.img bench/btrfs2 + mount -o loop bench/btrfs3.img bench/btrfs3 + $(MSG) Create some files + $(TESTENV) ./mktest$(EXEEXT) generate 1 btrfs 3 $(CHECKCOUNT) $(CHECKSIZE) + $(MSG) Basic +# Setup + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) scrub -p full + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check -d btrfs1 +# Recover test + $(MSG) Recover with file deletions from two disk with ONLY one parity OUTSIDE SYNC + rm -rf bench/btrfs1/a + rm -rf bench/btrfs2/a + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs1 -l test.log + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs2 -l test.log + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --force-full +# Recover test + $(MSG) Recover with file deletions from two disk with ONLY one parity aborted BEFORE SYNC +# Changes (including deletion) on disk1 + $(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/btrfs1/a/* bench/btrfs1/b/* +# Sync aborted BEFORE + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-before-sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) status +# Destroy disk2 + rm -rf bench/btrfs2/a bench/btrfs2/b +# Recover disk2 (disk1 is intentionally deleted, so nothing to recover) + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs2 -l test.log + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --force-full +# Recover test + $(MSG) Recover with file deletions from two disk with ONLY one parity aborted AFTER SYNC +# Changes (including deletion) on disk1 + $(TESTENV) ./mktest$(EXEEXT) change 2 500 bench/btrfs1/a/* bench/btrfs1/b/* +# Sync aborted AFTER + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-after-sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) status +# Destroy disk2 + rm -rf bench/btrfs2/a bench/btrfs2/b +# Recover disk2 (disk1 is intentionally deleted, so nothing to recover) + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs2 -l test.log + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --force-full +# Sync recover + $(MSG) Sync recover aborted AFTER +# Change files in disk3 + $(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/btrfs3/a/* +# Sync abort and sync retry + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-after-sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check +# Sync recover + $(MSG) Sync recover aborted BEFORE +# Change files in disk3 + $(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/btrfs3/a/* +# Sync abort and sync retry + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-before-sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync + $(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check + +btrfscheck_cleanup: + $(MSG) Unmount storages + umount bench/btrfs1 + umount bench/btrfs2 + umount bench/btrfs3 + +check-local: setupcheck mkdir bench/disk1 mkdir bench/disk2 mkdir bench/disk3 @@ -351,7 +408,7 @@ check-local: if HAVE_THREAD_CHECKER #### THREAD TEST #### $(MSG) Create some files - $(TESTENV) ./mktest$(EXEEXT) generate 1 6 $(CHECKCOUNT) $(CHECKSIZE) + $(TESTENV) ./mktest$(EXEEXT) generate 1 disk 6 $(CHECKCOUNT) $(CHECKSIZE) $(MSG) Some commands that use threads # First sync $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) sync @@ -493,7 +550,7 @@ endif $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) sync #### NOT EMPTY #### $(MSG) Create some files - $(TESTENV) ./mktest$(EXEEXT) generate 1 6 $(CHECKCOUNT) $(CHECKSIZE) + $(TESTENV) ./mktest$(EXEEXT) generate 1 disk 6 $(CHECKCOUNT) $(CHECKSIZE) echo DUP > bench/disk1/DUP1 echo DUP > bench/disk1/DUP2 echo -n > bench/disk1/ZERO @@ -568,7 +625,7 @@ endif rm bench/disk4/a/8* rm bench/disk5/a/8* rm bench/disk6/a/8* - $(TESTENV) ./mktest$(EXEEXT) generate 2 6 $(CHECKCOUNT) $(CHECKSIZE) + $(TESTENV) ./mktest$(EXEEXT) generate 2 disk 6 $(CHECKCOUNT) $(CHECKSIZE) mkdir bench/disk1/empty1 mkdir bench/disk2/empty2 mkdir bench/disk3/empty3a @@ -793,7 +850,7 @@ endif rm bench/disk2/a/2* rm bench/disk3/a/3* rm bench/disk4/a/4* - $(TESTENV) ./mktest$(EXEEXT) generate 3 6 $(CHECKCOUNT) $(CHECKSIZE) + $(TESTENV) ./mktest$(EXEEXT) generate 3 disk 6 $(CHECKCOUNT) $(CHECKSIZE) $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) sync $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) check $(MSG) Move some files, sync and check @@ -916,7 +973,7 @@ endif rm bench/disk4/a/6* $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) -B 10 sync $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) check -l test.log - $(TESTENV) ./mktest$(EXEEXT) generate 4 6 $(CHECKCOUNT) $(CHECKSIZE) + $(TESTENV) ./mktest$(EXEEXT) generate 4 disk 6 $(CHECKCOUNT) $(CHECKSIZE) $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) -B 100 sync $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) -B 1000 sync $(TESTENV) $(SNAPRAID) $(CHECKFLAGS) -c $(CONF) check -l test.log diff --git a/cmdline/mktest.c b/cmdline/mktest.c index ce3d6dce..2ee325e8 100644 --- a/cmdline/mktest.c +++ b/cmdline/mktest.c @@ -210,12 +210,12 @@ void cmd_generate_symlink(const char* path, const char* linkto) /** * Create a file or a symlink with a random name. */ -void cmd_generate(int disk, int size) +void cmd_generate(const char* tag, int disk, int size) { char path[PATH_MAX]; char* file; - snprintf(path, sizeof(path), "bench/disk%d/", disk); + snprintf(path, sizeof(path), "bench/%s%d/", tag, disk); file = path + strlen(path); /* add a directory */ @@ -627,9 +627,9 @@ void help(void) { printf("Test for " PACKAGE " v" VERSION " by Andrea Mazzoleni, " PACKAGE_URL "\n"); printf("Usage:\n"); - printf("\tmktest generate SEED DISK_NUM FILE_NUM FILE_SIZE\n"); - printf("\tmktest damage SEED NUM SIZE FILE\n"); - printf("\tmktest write SEED NUM SIZE FILE\n"); + printf("\tmktest generate SEED DISK_TAG DISK_NUM FILE_NUM FILE_SIZE\n"); + printf("\tmktest damage SEED COUNT SIZE FILE\n"); + printf("\tmktest write SEED COUNT SIZE FILE\n"); printf("\tmktest change SEED SIZE FILE\n"); printf("\tmktest append SEED SIZE FILE\n"); printf("\tmktest truncate SEED SIZE FILE\n"); @@ -648,8 +648,9 @@ int main(int argc, char* argv[]) if (strcmp(argv[1], "generate") == 0) { int disk, file, size; + const char* tag; - if (argc != 6) { + if (argc != 7) { /* LCOV_EXCL_START */ help(); exit(EXIT_FAILURE); @@ -657,20 +658,21 @@ int main(int argc, char* argv[]) } seed = atoi(argv[2]); - disk = atoi(argv[3]); - file = atoi(argv[4]); - size = atoi(argv[5]); + tag = argv[3]; + disk = atoi(argv[4]); + file = atoi(argv[5]); + size = atoi(argv[6]); for (i = 0; i < disk; ++i) { for (j = 0; j < file; ++j) { if (j == 0) /* create at least a big one */ - cmd_generate(i + 1, size); + cmd_generate(tag, i + 1, size); else if (j == 1) /* create at least an empty one */ - cmd_generate(i + 1, 0); + cmd_generate(tag, i + 1, 0); else - cmd_generate(i + 1, rnd(size)); + cmd_generate(tag, i + 1, rnd(size)); } } } else if (strcmp(argv[1], "write") == 0) { diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 819fc744..70c3308b 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -659,6 +659,7 @@ void log_close(const char* file) #define OPT_TEST_SPEED_PERIOD 307 #define OPT_TEST_SPEED_DISKS_NUMBER 308 #define OPT_TEST_SPEED_BLOCKS_SIZE 309 +#define OPT_TEST_KILL_BEFORE_SYNC 310 #if HAVE_GETOPT_LONG @@ -714,6 +715,9 @@ static struct option long_options[] = { /* After syncing, do not write the new content file */ { "test-kill-after-sync", 0, 0, OPT_TEST_KILL_AFTER_SYNC }, + /* Do not execure syncing after writing the new initial content file */ + { "test-kill-before-sync", 0, 0, OPT_TEST_KILL_BEFORE_SYNC }, + /* Exit with failure if after check/fix there ARE NOT unrecoverable errors. */ { "test-expect-unrecoverable", 0, 0, OPT_TEST_EXPECT_UNRECOVERABLE }, @@ -1267,6 +1271,9 @@ int snapraid_main(int argc, char* argv[]) case OPT_TEST_KILL_AFTER_SYNC : opt.kill_after_sync = 1; break; + case OPT_TEST_KILL_BEFORE_SYNC : + opt.kill_before_sync = 1; + break; case OPT_TEST_EXPECT_UNRECOVERABLE : opt.expect_unrecoverable = 1; break; diff --git a/cmdline/state.h b/cmdline/state.h index ef965289..20592cac 100644 --- a/cmdline/state.h +++ b/cmdline/state.h @@ -86,6 +86,7 @@ struct snapraid_option { int skip_disk_access; /**< Skip the data disk access for commands that don't need it. */ int skip_content_access; /**< Skip the content access for commands that don't need it. */ int kill_after_sync; /**< Kill the process after sync without saving the final state. */ + int kill_before_sync; /**< Kill the process before sync after saving the initial state. */ int force_murmur3; /**< Force Murmur3 choice. */ int force_spooky2; /**< Force Spooky2 choice. */ int force_order; /**< Force sorting order. One of the SORT_* defines. */ diff --git a/cmdline/sync.c b/cmdline/sync.c index ce178a27..56a9cf22 100644 --- a/cmdline/sync.c +++ b/cmdline/sync.c @@ -1431,14 +1431,15 @@ end: /* now the parity is fully written, no need to keep deallocated files */ state_commit(state); - /* save the new state if required */ - if (!state->opt.kill_after_sync) { - if ((state->need_write || state->opt.force_content_write)) - state_write(state); - } else { - log_fatal(EUSER, "WARNING! Skipped writing state due to --test-kill-after-sync option.\n"); + if (state->opt.kill_after_sync) { + log_fatal(EUSER, "WARNING! Killing due --test-kill-after-sync option.\n"); + exit(EXIT_SUCCESS); } + /* save the new state if required */ + if ((state->need_write || state->opt.force_content_write)) + state_write(state); + state_usage_print(state); if (soft_error || silent_error || io_error) { @@ -1677,6 +1678,11 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t log_fatal(EUSER, "WARNING! Skipped state write for --test-skip-content-write option.\n"); } + if (state->opt.kill_before_sync) { + log_fatal(EUSER, "WARNING! Killing due --test-kill-before-sync option.\n"); + exit(EXIT_SUCCESS); + } + /* skip degenerated cases of empty parity, or skipping all */ if (blockstart < blockmax) { ret = state_sync_process(state, parity_handle, blockstart, blockmax); diff --git a/test/test-btrfs.conf b/test/test-btrfs.conf new file mode 100644 index 00000000..51ab2516 --- /dev/null +++ b/test/test-btrfs.conf @@ -0,0 +1,9 @@ +blocksize 64 +parity bench/btrfs-parity +content bench/btrfs-content +content bench/btrfs-1-content +disk btrfs1 bench/btrfs1/ +disk btrfs2 bench/btrfs2/ +disk btrfs3 bench/btrfs3/ +snapshot +exclude .snapraid/