From 52b7f8753664e38f65332eb0decc52136041a01c Mon Sep 17 00:00:00 2001 From: CismonX Date: Wed, 2 Apr 2025 18:09:09 +0800 Subject: [PATCH] test: add tests for regular file read/write --- tests/Makefile.am | 9 +- tests/check_fs.c | 5 + tests/check_fs_regrw.c | 201 +++++++++++++++++++++++++++++++++++++++++ tests/check_util.h | 6 ++ tests/fs_regrw.at | 27 ++++++ tests/testsuite.at | 1 + 6 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 tests/check_fs_regrw.c create mode 100644 tests/fs_regrw.at diff --git a/tests/Makefile.am b/tests/Makefile.am index 3a7a02d..036a136 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,7 +9,7 @@ EXTRA_DIST = package.m4 testsuite.at $(TESTSUITE) $(TESTS_) TESTS_ = lib_hash.at lib_prng.at lib_watcher.at lib_sandbox.at \ - lib_hashmap.at fs_basic.at + lib_hashmap.at fs_basic.at fs_regrw.at # Helper programs for testing @@ -29,7 +29,14 @@ if BOOKMARKFS_MOUNT check_PROGRAMS += check-fs check_fs_CPPFLAGS = -I$(top_srcdir)/src + check_fs_LDADD = check_fs_SOURCES = check_fs.c + +if BOOKMARKFS_UTIL + check_fs_CPPFLAGS += -DHAVE_BOOKMARKFS_UTIL + check_fs_LDADD += $(top_builddir)/src/libbookmarkfs_util.la + check_fs_SOURCES += check_fs_regrw.c check_util.c +endif # BOOKMARKFS_UTIL endif # BOOKMARKFS_MOUNT # Autotest setup diff --git a/tests/check_fs.c b/tests/check_fs.c index 4084fd0..47fd85e 100644 --- a/tests/check_fs.c +++ b/tests/check_fs.c @@ -29,6 +29,7 @@ #include #include +#include "check_util.h" #include "frontend_util.h" // Forward declaration start @@ -53,6 +54,10 @@ dispatch_subcmds ( status = subcmd_ismount(argc, argv); } else if (0 == strcmp("sleep", cmd)) { status = subcmd_sleep(argc, argv); +#ifdef HAVE_BOOKMARKFS_UTIL + } else if (0 == strcmp("regrw", cmd)) { + status = check_fs_regrw(argc, argv); +#endif } else { log_printf("bad subcmd '%s'", cmd); } diff --git a/tests/check_fs_regrw.c b/tests/check_fs_regrw.c new file mode 100644 index 0000000..eece06c --- /dev/null +++ b/tests/check_fs_regrw.c @@ -0,0 +1,201 @@ +/** + * bookmarkfs/tests/check_fs_regrw.c + * ---- + * + * Copyright (C) 2025 CismonX + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#include +#include +#include +#include + +#include "check_util.h" +#include "frontend_util.h" +#include "prng.h" + +// Forward declaration start +static int check_file_data (int, void *, off_t); +static off_t check_rand_rw (int, uint64_t *, size_t); +static int do_check_fs_regrw (char const *, size_t); +// Forward declaration end + +static int +check_file_data ( + int fd, + void *buf, + off_t file_size +) { + void *file_data = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (file_data == MAP_FAILED) { + log_puts("mmap() failed"); + return -1; + } + int result = memcmp(file_data, buf, file_size); + + munmap(file_data, file_size); + return result; +} + +static off_t +check_rand_rw ( + int fd, + uint64_t *buf, + size_t items +) { +#define MAX_CNT 2048 + uint64_t tmp_buf[MAX_CNT]; + + off_t file_size = 0; + for (size_t i = 0; i < items; ++i) { + uint64_t bits = prng_rand(); + size_t off = bits % items; + size_t cnt = (bits >> 32) % MAX_CNT + 1; + if (off + cnt > items) { + cnt = items - off; + } + ssize_t obj_bytes = sizeof(uint64_t) * cnt; + off_t off_bytes = sizeof(uint64_t) * off; + + if (off_bytes + obj_bytes <= file_size) { + if (obj_bytes != pread(fd, tmp_buf, obj_bytes, off_bytes)) { + log_puts("pread() failed"); + return -1; + } + if (0 != memcmp(tmp_buf, buf + off, obj_bytes)) { + log_puts("data not match"); + return -1; + } + } + + for (size_t j = off; j < off + cnt; ++j) { + buf[j] = prng_rand(); + } + if (obj_bytes != pwrite(fd, buf + off, obj_bytes, off_bytes)) { + log_puts("pwrite() failed"); + return -1; + } + if (file_size < off_bytes + obj_bytes) { + file_size = off_bytes + obj_bytes; + } + } + return file_size; +} + +static int +do_check_fs_regrw ( + char const *path, + size_t file_max +) { +#define ASSERT_EQ(val, expr) ASSERT_EXPR_INT(expr, r_, (val) == r_, goto end;) +#define ASSERT_NE(val, expr) ASSERT_EXPR_INT(expr, r_, (val) != r_, goto end;) + + void *buf = mmap(NULL, file_max, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) { + log_puts("failed to allocate memory"); + return -1; + } + int status = -1; + + // Random read/write testing is less useful without O_DIRECT, + // due to the page cache. +#ifndef O_DIRECT +# define O_DIRECT 0 +#endif + int fd = open(path, O_RDWR | O_TRUNC | O_DIRECT); + ASSERT_NE(-1, fd); + + struct stat stat_buf; + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_EQ(0, stat_buf.st_size); + + off_t nbytes = check_rand_rw(fd, buf, file_max / sizeof(uint64_t)); + ASSERT_NE(-1, nbytes); + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_EQ(nbytes, stat_buf.st_size); + + // File data must be valid URI to be persisted to bookmark storage. + memcpy(buf, "foo:bar/", 8); + uint8_t lut[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" + "0123456789" "-_"; + for (uint8_t *b = (uint8_t *)buf + 8; b < (uint8_t *)buf + nbytes; ++b) { + *b = lut[*b & 0x3f]; + } + ASSERT_EQ(nbytes, pwrite(fd, buf, nbytes, 0)); + ASSERT_EQ(0, fsync(fd)); + + // Re-open the file and check if data is still valid. + close(fd); + fd = open(path, O_RDONLY | O_DIRECT); + ASSERT_NE(-1, fd); + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_EQ(nbytes, stat_buf.st_size); + ASSERT_EQ(0, check_file_data(fd, buf, nbytes)); + + status = 0; + + end: + if (fd >= 0) { + close(fd); + } + munmap(buf, file_max); + return status; +} + +int +check_fs_regrw ( + int argc, + char *argv[] +) { + uint64_t seed_buf[4], *seed = NULL; + int file_max = -1; + + OPT_START(argc, argv, "n:s:") + OPT_OPT('n') { + file_max = atoi(optarg); + break; + } + OPT_OPT('s') { + if (0 != prng_seed_from_hex(seed_buf, optarg)) { + return -1; + } + seed = seed_buf; + break; + } + OPT_END + + if (file_max <= 0 || file_max % sizeof(uint64_t) != 0) { + log_printf("bad size %d", file_max); + return -1; + } + if (argc < 1) { + log_puts("path not given"); + return -1; + } + char const *path = argv[0]; + + if (0 != prng_seed(seed)) { + return -1; + } + return do_check_fs_regrw(path, file_max); +} diff --git a/tests/check_util.h b/tests/check_util.h index 68cbaed..c9dc35c 100644 --- a/tests/check_util.h +++ b/tests/check_util.h @@ -35,6 +35,12 @@ action_if_false \ } while (0) +int +check_fs_regrw ( + int argc, + char *argv[] +); + int check_hashmap ( int argc, diff --git a/tests/fs_regrw.at b/tests/fs_regrw.at new file mode 100644 index 0000000..85a73ac --- /dev/null +++ b/tests/fs_regrw.at @@ -0,0 +1,27 @@ +dnl +dnl Copyright (C) 2025 CismonX +dnl +dnl Copying and distribution of this file, with or without modification, +dnl are permitted in any medium without royalty, +dnl provided the copyright notice and this notice are preserved. +dnl This file is offered as-is, without any warranty. +dnl + +AT_SETUP([fs: regular file read/write]) +AT_KEYWORDS([fs regrw]) + +ATX_CHECK_FS_NEW_ANY([file_max=524288], [ + # requires PRNG + ATX_FEAT_PREREQ([bookmarkfs-util]) +], [ + name=$(ath_fn_rand_u64_hex) + seed=$(ath_fn_prng_seed) + echo "prng seed: $seed" + + ATX_RUN([ + touch $name + check-fs regrw -n 524288 -s "$seed" $name + ]) +]) + +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 9b07014..da087b7 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -205,3 +205,4 @@ m4_include([lib_hashmap.at]) AT_BANNER([The Filesystem]) m4_include([fs_basic.at]) +m4_include([fs_regrw.at])