mirror of
https://git.sr.ht/~cismonx/bookmarkfs
synced 2025-06-07 19:58:50 +00:00
test: add tests for the file watcher
This commit is contained in:
parent
0b7b46be9c
commit
3a0f435572
6 changed files with 262 additions and 5 deletions
|
@ -8,10 +8,11 @@
|
|||
|
||||
EXTRA_DIST = package.m4 testsuite.at $(TESTSUITE) $(TESTS_)
|
||||
|
||||
TESTS_ = lib_hash.at lib_prng.at
|
||||
TESTS_ = lib_hash.at lib_prng.at lib_watcher.at
|
||||
|
||||
# Helper programs for testing
|
||||
|
||||
check_HEADERS = check_lib.h
|
||||
check_PROGRAMS =
|
||||
|
||||
if BOOKMARKFS_UTIL
|
||||
|
@ -19,7 +20,7 @@ if BOOKMARKFS_UTIL
|
|||
|
||||
check_bookmarkfs_util_CPPFLAGS = -I$(top_srcdir)/src
|
||||
check_bookmarkfs_util_LDADD = $(top_builddir)/src/libbookmarkfs_util.la
|
||||
check_bookmarkfs_util_SOURCES = check_lib.c
|
||||
check_bookmarkfs_util_SOURCES = check_lib.c check_watcher.c
|
||||
endif # BOOKMARKFS_UTIL
|
||||
|
||||
# Autotest setup
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include "check_lib.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -55,6 +57,8 @@ dispatch_subcmds (
|
|||
status = subcmd_hash(argc, argv);
|
||||
} else if (0 == strcmp("prng", cmd)) {
|
||||
status = subcmd_prng(argc, argv);
|
||||
} else if (0 == strcmp("watcher", cmd)) {
|
||||
status = check_watcher(argc, argv);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
|
38
tests/check_lib.h
Normal file
38
tests/check_lib.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* bookmarkfs/tests/check_lib.h
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2025 CismonX <admin@cismon.net>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOOKMARKFS_CHECK_LIB_H_
|
||||
#define BOOKMARKFS_CHECK_LIB_H_
|
||||
|
||||
#include "xstd.h"
|
||||
|
||||
#define ASSERT_EXPR(cond, action_if_false) \
|
||||
if (!(cond)) { \
|
||||
log_printf("assertion (%s) failed", #cond); \
|
||||
action_if_false \
|
||||
}
|
||||
|
||||
int
|
||||
check_watcher (
|
||||
int argc,
|
||||
char *argv[]
|
||||
);
|
||||
|
||||
#endif /* !defined(BOOKMARKFS_CHECK_LIB_H_) */
|
184
tests/check_watcher.c
Normal file
184
tests/check_watcher.c
Normal file
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* bookmarkfs/tests/check_watcher.c
|
||||
* ----
|
||||
*
|
||||
* Copyright (C) 2025 CismonX <admin@cismon.net>
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "check_lib.h"
|
||||
#include "frontend_util.h"
|
||||
#include "sandbox.h"
|
||||
#include "watcher.h"
|
||||
|
||||
// Forward declaration start
|
||||
static int do_check_watcher (int, uint32_t);
|
||||
static void msecs_to_timespec (struct timespec *, unsigned long);
|
||||
static int wait_for_watcher (struct watcher *, struct timespec const *, int);
|
||||
// Forward declaration end
|
||||
|
||||
static int
|
||||
do_check_watcher (
|
||||
int dirfd,
|
||||
uint32_t flags
|
||||
) {
|
||||
#define FILE1_NAME "foo.tmp"
|
||||
#define FILE2_NAME "bar.tmp"
|
||||
|
||||
#define ASSERT_EQ(val, expr) ASSERT_EXPR((val) == (expr), goto end;)
|
||||
#define ASSERT_NE(val, expr) ASSERT_EXPR((val) != (expr), goto end;)
|
||||
|
||||
struct watcher *w = watcher_create(dirfd, FILE1_NAME, flags);
|
||||
if (w == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned long msecs = 100;
|
||||
int tries = 5;
|
||||
if (flags & WATCHER_FALLBACK) {
|
||||
msecs = 2500;
|
||||
tries = 2;
|
||||
}
|
||||
struct timespec ts;
|
||||
msecs_to_timespec(&ts, msecs);
|
||||
|
||||
int status = -1;
|
||||
int fd = -1;
|
||||
|
||||
fd = openat(dirfd, FILE1_NAME, O_WRONLY | O_CREAT, 0600);
|
||||
ASSERT_NE(-1, fd);
|
||||
// Lazy-init watcher.
|
||||
ASSERT_EQ(0, wait_for_watcher(w, &ts, tries));
|
||||
|
||||
ASSERT_NE(-1, write(fd, "foo", 3));
|
||||
ASSERT_EQ(0, wait_for_watcher(w, &ts, tries));
|
||||
|
||||
bool check_truncate = true;
|
||||
#if defined(__FreeBSD__)
|
||||
// For kevent() EVFILT_VNODE, ftruncate() only triggers NOTE_ATTRIB,
|
||||
// which we don't want to watch.
|
||||
check_truncate = flags & WATCHER_FALLBACK;
|
||||
#endif
|
||||
if (check_truncate) {
|
||||
ASSERT_EQ(0, ftruncate(fd, 0));
|
||||
ASSERT_EQ(0, wait_for_watcher(w, &ts, tries));
|
||||
}
|
||||
|
||||
int fd2 = openat(dirfd, FILE2_NAME, O_WRONLY | O_CREAT, 0600);
|
||||
ASSERT_NE(-1, fd2);
|
||||
close(fd2);
|
||||
|
||||
// FAN_DELETE_SELF won't fire if the watched file
|
||||
// is still opened somewhere.
|
||||
close(fd);
|
||||
fd = -1;
|
||||
ASSERT_EQ(0, renameat(dirfd, FILE2_NAME, dirfd, FILE1_NAME));
|
||||
ASSERT_EQ(0, wait_for_watcher(w, &ts, tries));
|
||||
|
||||
ASSERT_EQ(0, renameat(dirfd, FILE1_NAME, dirfd, FILE2_NAME));
|
||||
ASSERT_EQ(-ENOENT, wait_for_watcher(w, &ts, tries));
|
||||
|
||||
// If the watched file has gone, but managed to come back,
|
||||
// the watcher should continue to work.
|
||||
ASSERT_EQ(0, renameat(dirfd, FILE2_NAME, dirfd, FILE1_NAME));
|
||||
ASSERT_EQ(0, wait_for_watcher(w, &ts, tries));
|
||||
|
||||
ASSERT_EQ(0, unlinkat(dirfd, FILE1_NAME, 0));
|
||||
ASSERT_EQ(-ENOENT, wait_for_watcher(w, &ts, tries));
|
||||
|
||||
status = 0;
|
||||
|
||||
end:
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
unlinkat(dirfd, FILE1_NAME, 0);
|
||||
unlinkat(dirfd, FILE2_NAME, 0);
|
||||
watcher_destroy(w);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
msecs_to_timespec (
|
||||
struct timespec *ts_buf,
|
||||
unsigned long millisecs
|
||||
) {
|
||||
ts_buf->tv_sec = millisecs / 1000;
|
||||
ts_buf->tv_nsec = (millisecs % 1000) * 1000000;
|
||||
}
|
||||
|
||||
static int
|
||||
wait_for_watcher (
|
||||
struct watcher *w,
|
||||
struct timespec const *ts,
|
||||
int retry
|
||||
) {
|
||||
for (int i = 0; i < retry; ++i) {
|
||||
clock_nanosleep(CLOCK_MONOTONIC, 0, ts, NULL);
|
||||
|
||||
int status = watcher_poll(w);
|
||||
if (status != -EAGAIN) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
int
|
||||
check_watcher (
|
||||
int argc,
|
||||
char *argv[]
|
||||
) {
|
||||
uint32_t flags = 0;
|
||||
#if defined(__FreeBSD__)
|
||||
// Do not enable sandbox on FreeBSD,
|
||||
// since the watcher sandbox only grants read access to dirfd,
|
||||
// and cap_enter() applies to the entire process.
|
||||
flags |= SANDBOX_NOOP << WATCHER_SANDBOX_FLAGS_OFFSET;
|
||||
#endif
|
||||
|
||||
getopt_foreach(argc, argv, ":f") {
|
||||
case 'f':
|
||||
flags |= WATCHER_FALLBACK;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
if (argc < 1) {
|
||||
return -1;
|
||||
}
|
||||
argv += optind;
|
||||
|
||||
int dirfd = open(argv[0], O_RDONLY | O_DIRECTORY);
|
||||
if (dirfd < 0) {
|
||||
return -1;
|
||||
}
|
||||
int status = do_check_watcher(dirfd, flags);
|
||||
close(dirfd);
|
||||
return status;
|
||||
}
|
25
tests/lib_watcher.at
Normal file
25
tests/lib_watcher.at
Normal file
|
@ -0,0 +1,25 @@
|
|||
dnl
|
||||
dnl Copyright (C) 2025 CismonX <admin@cismon.net>
|
||||
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([util lib: file watcher])
|
||||
AT_KEYWORDS([lib watcher])
|
||||
|
||||
ATX_CHECK_LIB([
|
||||
args=
|
||||
ATX_FEAT_IF([native-watcher], , [
|
||||
args="$args -f"
|
||||
])
|
||||
|
||||
tmpdir=./$(ath_fn_rand_u64_hex).tmp.d
|
||||
|
||||
mkdir $tmpdir
|
||||
check-bookmarkfs-util watcher $args $tmpdir
|
||||
])
|
||||
|
||||
AT_CLEANUP
|
|
@ -28,9 +28,9 @@ dnl
|
|||
dnl Conditionally run script depending on whether a feature is enabled.
|
||||
dnl
|
||||
m4_define([ATX_FEAT_IF], [
|
||||
if test -n "$ATX_FEAT_NAME($1)"; then
|
||||
if test -n "$ATX_FEAT_NAME($1)"; then :
|
||||
$2
|
||||
else
|
||||
else :
|
||||
$3
|
||||
fi
|
||||
])
|
||||
|
@ -47,7 +47,11 @@ m4_define([ATX_FEAT_PREREQ], [
|
|||
])
|
||||
|
||||
m4_define([ATX_CHECK_SIMPLE], [
|
||||
AT_CHECK([$1], , [ignore], [ignore])
|
||||
AT_CHECK([
|
||||
atx_line_start=$LINENO
|
||||
trap 'echo "Error $? at line $(($LINENO-$atx_line_start))"; exit 1' ERR
|
||||
$1
|
||||
], , [ignore], [ignore])
|
||||
])
|
||||
|
||||
m4_define([ATX_CHECK_LIB], [
|
||||
|
@ -70,3 +74,4 @@ AT_TEST_HELPER_FN([rand_base64], , , [
|
|||
AT_BANNER([The Utility Library])
|
||||
m4_include([lib_hash.at])
|
||||
m4_include([lib_prng.at])
|
||||
m4_include([lib_watcher.at])
|
||||
|
|
Loading…
Add table
Reference in a new issue