From c23623b5a36e54aca4761ea4c5110640a765c2d0 Mon Sep 17 00:00:00 2001 From: CismonX Date: Sun, 8 Jun 2025 12:34:10 +0800 Subject: [PATCH] test: add tests for file atime/mtime --- tests/Makefile.am | 6 +- tests/check_fs.c | 2 + tests/check_fs_times.c | 194 +++++++++++++++++++++++++++++++++++++++++ tests/check_util.h | 6 ++ tests/fs_times.at | 27 ++++++ tests/testsuite.at | 1 + 6 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 tests/check_fs_times.c create mode 100644 tests/fs_times.at diff --git a/tests/Makefile.am b/tests/Makefile.am index 34ab0a5..12ace8b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,7 +9,8 @@ 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 fs_regrw.at fs_dents.at fs_assoc.at + lib_hashmap.at fs_basic.at fs_regrw.at fs_dents.at fs_assoc.at \ + fs_times.at # Helper programs for testing @@ -35,7 +36,8 @@ if BOOKMARKFS_MOUNT 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_fs_dents.c check_util.c + check_fs_SOURCES += check_fs_regrw.c check_fs_dents.c check_util.c \ + check_fs_times.c endif # BOOKMARKFS_UTIL endif # BOOKMARKFS_MOUNT diff --git a/tests/check_fs.c b/tests/check_fs.c index 2fa9717..3c73c77 100644 --- a/tests/check_fs.c +++ b/tests/check_fs.c @@ -60,6 +60,8 @@ dispatch_subcmds ( } else if (0 == strcmp("dents", cmd)) { status = check_fs_dents(argc, argv); #endif + } else if (0 == strcmp("times", cmd)) { + status = check_fs_times(argc, argv); } else { log_printf("bad subcmd '%s'", cmd); } diff --git a/tests/check_fs_times.c b/tests/check_fs_times.c new file mode 100644 index 0000000..c1fdb63 --- /dev/null +++ b/tests/check_fs_times.c @@ -0,0 +1,194 @@ +/** + * bookmarkfs/tests/check_fs_times.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 + +#include "check_util.h" +#include "frontend_util.h" +#include "prng.h" + +// Forward declaration start +static int compare_timespec (struct timespec, struct timespec); +static int do_check_fs_times (int, int); +static void usecs_to_timespec (struct timespec *, uint32_t); +// Forward declaration end + +static int +compare_timespec ( + struct timespec ts1, + struct timespec ts2 +) { +#define timespec_to_usecs(ts) ( (ts).tv_sec * 1000000 + (ts).tv_nsec / 1000 ) + int64_t usecs1 = timespec_to_usecs(ts1); + int64_t usecs2 = timespec_to_usecs(ts2); + + if (usecs1 == usecs2) { + return 0; + } + return usecs2 > usecs1 ? 1 : -1; +} + +static int +do_check_fs_times ( + int dirfd, + int rounds +) { +#define FILE1_NAME "foo.tmp" +#define FILE2_NAME "bar.tmp" + +#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;) + + int status = -1; + + struct timespec now; + ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now)); + + int fd = openat(dirfd, FILE1_NAME, O_WRONLY | O_CREAT | O_EXCL); + ASSERT_NE(-1, fd); + + struct stat stat_buf; + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + + ASSERT_EQ(0, fstat(dirfd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + now = stat_buf.st_mtim; + + ASSERT_EQ(11, write(fd, "foo:bar/baz", 11)); + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + + ASSERT_EQ(0, renameat(dirfd, FILE1_NAME, dirfd, FILE2_NAME)); + ASSERT_EQ(0, fstat(dirfd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + now = stat_buf.st_mtim; + + ASSERT_EQ(0, ftruncate(fd, 10)); + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + + struct timespec times[2]; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_nsec = UTIME_NOW; + ASSERT_EQ(0, futimens(dirfd, times)); + ASSERT_EQ(0, fstat(dirfd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + now = stat_buf.st_mtim; + + times[0].tv_nsec = UTIME_NOW; + times[1].tv_nsec = UTIME_OMIT; + ASSERT_EQ(0, futimens(fd, times)); + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_atim)); + + ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now)); + ASSERT_NE(-1, compare_timespec(stat_buf.st_atim, now)); + + for (int i = 0; i < rounds; ++i) { + uint64_t bits = prng_rand(); + usecs_to_timespec(×[0], bits & 0xffffffff); + usecs_to_timespec(×[1], bits >> 32); + + ASSERT_EQ(0, futimens(fd, times)); + ASSERT_EQ(0, fstat(fd, &stat_buf)); + ASSERT_EQ(0, compare_timespec(times[0], stat_buf.st_atim)); + ASSERT_EQ(0, compare_timespec(times[1], stat_buf.st_mtim)); + } + + ASSERT_EQ(0, unlinkat(dirfd, FILE2_NAME, 0)); + ASSERT_EQ(0, fstat(dirfd, &stat_buf)); + ASSERT_NE(-1, compare_timespec(now, stat_buf.st_mtim)); + + ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, &now)); + ASSERT_NE(-1, compare_timespec(stat_buf.st_mtim, now)); + + status = 0; + + end: + if (fd >= 0) { + close(fd); + } + return status; +} + +static void +usecs_to_timespec ( + struct timespec *ts_buf, + uint32_t usecs +) { + ts_buf->tv_sec = usecs / 1000000; + ts_buf->tv_nsec = (usecs % 1000000) * 1000; +} + +int +check_fs_times ( + int argc, + char *argv[] +) { + uint64_t seed_buf[4], *seed = NULL; + int rounds = -1; + + OPT_START(argc, argv, "r:s:") + OPT_OPT('r') { + rounds = atoi(optarg); + break; + } + OPT_OPT('s') { + if (0 != prng_seed_from_hex(seed_buf, optarg)) { + return -1; + } + seed = seed_buf; + break; + } + OPT_END + + if (rounds < 0) { + log_printf("bad rounds cnt %d", rounds); + return -1; + } + if (argc < 1) { + log_puts("path not given"); + return -1; + } + char const *path = argv[0]; + + if (0 != prng_seed(seed)) { + return -1; + } + int dirfd = open(path, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) { + log_printf("open: %s: %s", path, strerror(errno)); + return -1; + } + int status = do_check_fs_times(dirfd, rounds); + close(dirfd); + return status; +} diff --git a/tests/check_util.h b/tests/check_util.h index 0b5a0fd..f318bb2 100644 --- a/tests/check_util.h +++ b/tests/check_util.h @@ -41,6 +41,12 @@ check_fs_dents ( char *argv[] ); +int +check_fs_times ( + int argc, + char *argv[] +); + int check_fs_regrw ( int argc, diff --git a/tests/fs_times.at b/tests/fs_times.at new file mode 100644 index 0000000..b193753 --- /dev/null +++ b/tests/fs_times.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: last access/modification times]) +AT_KEYWORDS([fs times]) + +ATX_CHECK_FS_NEW_ANY(, [ + # requires PRNG + ATX_FEAT_PREREQ([bookmarkfs-util]) +], [ + name=$(ath_fn_rand_u64_hex) + seed=$(ath_fn_prng_seed) + echo "prng seed: $seed" + + mkdir $name + ATX_RUN([ + check-fs times -s "$seed" -r 512 $name + ]) +]) + +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index f0e8fed..cc7c363 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -220,3 +220,4 @@ m4_include([fs_basic.at]) m4_include([fs_regrw.at]) m4_include([fs_dents.at]) m4_include([fs_assoc.at]) +m4_include([fs_times.at])