Add regression check for Btrfs

It requires root privileges as it has to create Btrfs filesystems.
This commit is contained in:
Andrea Mazzoleni 2026-04-28 11:51:59 +02:00
parent 2e71602267
commit 76ce1cbb16
7 changed files with 142 additions and 59 deletions

View file

@ -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:

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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. */

View file

@ -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);

9
test/test-btrfs.conf Normal file
View file

@ -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/