From: Stephan Bosch Date: Mon, 27 Oct 2025 20:48:35 +0000 (+0100) Subject: lib-test: test-subprocess - Move most signal and cleanup code to test-common for... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3c086d4612598b06baadf2e6c2f443cb1bef0158;p=thirdparty%2Fdovecot%2Fcore.git lib-test: test-subprocess - Move most signal and cleanup code to test-common for re-use --- diff --git a/src/imap/test-imap-client-hibernate.c b/src/imap/test-imap-client-hibernate.c index 873c928476..3f18b966e4 100644 --- a/src/imap/test-imap-client-hibernate.c +++ b/src/imap/test-imap-client-hibernate.c @@ -282,8 +282,6 @@ static void test_tmp_dir_init(void) test_cleanup(); if (mkdir(tmpdir, 0700) < 0) i_fatal("mkdir() failed: %m"); - - test_subprocesses_init(FALSE); } struct test_service_settings { @@ -324,6 +322,7 @@ int main(int argc, char *argv[]) master_service_init_finish(master_service); test_tmp_dir_init(); + test_subprocesses_init(FALSE); static void (*const test_functions[])(void) = { test_imap_client_hibernate, @@ -331,7 +330,6 @@ int main(int argc, char *argv[]) }; ret = test_run(test_functions); - test_subprocesses_deinit(); test_cleanup(); master_service_deinit(&master_service); return ret; diff --git a/src/lib-auth-client/test-auth-client.c b/src/lib-auth-client/test-auth-client.c index 2d8333a688..2007f10b1f 100644 --- a/src/lib-auth-client/test-auth-client.c +++ b/src/lib-auth-client/test-auth-client.c @@ -1267,12 +1267,12 @@ int main(int argc, char *argv[]) } } + test_init(); + test_set_cleanup_callback(main_cleanup); test_subprocesses_init(debug); - test_subprocess_set_cleanup_callback(main_cleanup); ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); return ret; diff --git a/src/lib-auth-client/test-auth-master.c b/src/lib-auth-client/test-auth-master.c index 7fa7089856..596fa01e93 100644 --- a/src/lib-auth-client/test-auth-master.c +++ b/src/lib-auth-client/test-auth-master.c @@ -1921,12 +1921,12 @@ int main(int argc, char *argv[]) } } + test_init(); + test_set_cleanup_callback(main_cleanup); test_subprocesses_init(debug); - test_subprocess_set_cleanup_callback(main_cleanup); ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); return ret; diff --git a/src/lib-http/test-http-client-errors.c b/src/lib-http/test-http-client-errors.c index d80ad43483..38c9f48e32 100644 --- a/src/lib-http/test-http-client-errors.c +++ b/src/lib-http/test-http-client-errors.c @@ -3818,6 +3818,7 @@ int main(int argc, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -3827,7 +3828,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); event_set_ptr(cctx->event, SETTINGS_EVENT_ROOT, NULL); main_deinit(); diff --git a/src/lib-http/test-http-payload.c b/src/lib-http/test-http-payload.c index ada971dcd1..1c5f290484 100644 --- a/src/lib-http/test-http-payload.c +++ b/src/lib-http/test-http-payload.c @@ -2476,6 +2476,7 @@ int main(int argc, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -2485,7 +2486,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); return ret; diff --git a/src/lib-http/test-http-server-errors.c b/src/lib-http/test-http-server-errors.c index 98336284a2..efb15dedc3 100644 --- a/src/lib-http/test-http-server-errors.c +++ b/src/lib-http/test-http-server-errors.c @@ -1026,6 +1026,7 @@ int main(int argc, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -1035,7 +1036,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); diff --git a/src/lib-imap-client/test-imapc-client.c b/src/lib-imap-client/test-imapc-client.c index e8ca2be8bd..e1bf9c8ba3 100644 --- a/src/lib-imap-client/test-imapc-client.c +++ b/src/lib-imap-client/test-imapc-client.c @@ -960,6 +960,7 @@ int main(int argc ATTR_UNUSED, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -969,7 +970,6 @@ int main(int argc ATTR_UNUSED, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); diff --git a/src/lib-login/test-login-server-auth.c b/src/lib-login/test-login-server-auth.c index 631c000c57..97caa218e3 100644 --- a/src/lib-login/test-login-server-auth.c +++ b/src/lib-login/test-login-server-auth.c @@ -981,12 +981,13 @@ int main(int argc, char *argv[]) } master_service_init_finish(master_service); + + test_init(); + test_set_cleanup_callback(main_cleanup); test_subprocesses_init(debug); - test_subprocess_set_cleanup_callback(main_cleanup); ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); master_service_deinit(&master_service); diff --git a/src/lib-lua/test-lua-http-client.c b/src/lib-lua/test-lua-http-client.c index 198ee0ecc1..216d6a4a5c 100644 --- a/src/lib-lua/test-lua-http-client.c +++ b/src/lib-lua/test-lua-http-client.c @@ -813,6 +813,7 @@ int main(int argc, char *argv[]) master_service_init_finish(master_service); + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -827,7 +828,6 @@ int main(int argc, char *argv[]) event_unref(&test_event); - test_subprocesses_deinit(); main_deinit(); master_service_deinit(&master_service); diff --git a/src/lib-smtp/test-smtp-client-errors.c b/src/lib-smtp/test-smtp-client-errors.c index 07ba6c535e..abb69c7bc5 100644 --- a/src/lib-smtp/test-smtp-client-errors.c +++ b/src/lib-smtp/test-smtp-client-errors.c @@ -4343,6 +4343,7 @@ int main(int argc, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -4352,7 +4353,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); diff --git a/src/lib-smtp/test-smtp-payload.c b/src/lib-smtp/test-smtp-payload.c index 16f1aa196c..98724eeeb7 100644 --- a/src/lib-smtp/test-smtp-payload.c +++ b/src/lib-smtp/test-smtp-payload.c @@ -1536,6 +1536,7 @@ int main(int argc, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -1545,7 +1546,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); diff --git a/src/lib-smtp/test-smtp-server-errors.c b/src/lib-smtp/test-smtp-server-errors.c index 3113b94b11..804ecb0c10 100644 --- a/src/lib-smtp/test-smtp-server-errors.c +++ b/src/lib-smtp/test-smtp-server-errors.c @@ -4167,6 +4167,7 @@ int main(int argc, char *argv[]) } } + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -4176,7 +4177,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); lib_deinit(); diff --git a/src/lib-smtp/test-smtp-submit.c b/src/lib-smtp/test-smtp-submit.c index 28a90970b6..dab6c45761 100644 --- a/src/lib-smtp/test-smtp-submit.c +++ b/src/lib-smtp/test-smtp-submit.c @@ -2189,6 +2189,8 @@ int main(int argc, char *argv[]) } master_service_init_finish(master_service); + + test_init(); test_subprocesses_init(debug); /* listen on localhost */ @@ -2198,7 +2200,6 @@ int main(int argc, char *argv[]) ret = test_run(test_functions); - test_subprocesses_deinit(); main_deinit(); master_service_deinit(&master_service); diff --git a/src/lib-test/Makefile.am b/src/lib-test/Makefile.am index cb6c66edb9..bc4515e9f3 100644 --- a/src/lib-test/Makefile.am +++ b/src/lib-test/Makefile.am @@ -16,6 +16,7 @@ headers = \ fuzzer.h \ ostream-final-trickle.h \ test-common.h \ + test-private.h \ test-subprocess.h pkginc_libdir=$(pkgincludedir) diff --git a/src/lib-test/test-common.c b/src/lib-test/test-common.c index e556bf0ad2..9ae966c351 100644 --- a/src/lib-test/test-common.c +++ b/src/lib-test/test-common.c @@ -1,14 +1,20 @@ /* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "lib-signals.h" #include "str.h" #include "safe-mkstemp.h" #include "test-common.h" +#include "test-private.h" +#include "test-subprocess.h" +#include #include #include /* for fatal tests */ -static bool test_deinit_lib; +static bool test_initialized = FALSE; +static bool test_deinit_lib = FALSE; +static bool test_deinit_lib_signals = FALSE; /* To test the firing of i_assert, we need non-local jumps, i.e. setjmp */ static volatile bool expecting_fatal = FALSE; @@ -18,12 +24,15 @@ static jmp_buf fatal_jmpbuf; static char *test_prefix; static bool test_success; +static bool test_running; static unsigned int failure_count; static unsigned int total_count; static unsigned int expected_errors; static char *expected_error_str, *expected_fatal_str; static test_fatal_callback_t *test_fatal_callback; static void *test_fatal_context; +static void (*test_cleanup_callback)(void) = NULL; +volatile sig_atomic_t terminating = 0; void test_begin(const char *name) { @@ -356,8 +365,52 @@ test_fatal_handler(const struct failure_context *ctx, i_unreached(); /* we simply can't get here */ } -static void test_init(void) +static void test_cleanup(void) { + if (test_subprocess_is_child()) { + /* Child processes must not execute the cleanups */ + return; + } + + test_subprocess_cleanup(); + + /* Perform any additional important cleanup specific to the test. */ + if (test_cleanup_callback != NULL) + test_cleanup_callback(); +} + +static void test_terminate(const siginfo_t *si, void *context ATTR_UNUSED) +{ + int signo = si->si_signo; + + if (terminating != 0) + raise(signo); + terminating = 1; + + /* Perform important cleanups */ + test_cleanup(); + + (void)signal(signo, SIG_DFL); + if (signo == SIGTERM) + _exit(0); + else + raise(signo); +} + +static void test_atexit(void) +{ + /* NOTICE: This is also called by children, so be careful. */ + + /* Perform important cleanups */ + test_cleanup(); +} + +void test_init(void) +{ + if (test_initialized) + return; + test_initialized = TRUE; + test_prefix = NULL; failure_count = 0; total_count = 0; @@ -372,12 +425,31 @@ static void test_init(void) /* Don't set fatal handler until actually needed for fatal testing */ } +void test_init_signals(void) +{ + lib_signals_init(); + test_deinit_lib_signals = TRUE; + + atexit(test_atexit); + lib_signals_ignore(SIGPIPE, TRUE); + lib_signals_set_handler(SIGTERM, 0, test_terminate, NULL); + lib_signals_set_handler(SIGQUIT, 0, test_terminate, NULL); + lib_signals_set_handler(SIGINT, 0, test_terminate, NULL); + lib_signals_set_handler(SIGSEGV, 0, test_terminate, NULL); + lib_signals_set_handler(SIGABRT, 0, test_terminate, NULL); +} + static int test_deinit(void) { i_assert(test_prefix == NULL); printf("%u / %u tests failed\n", failure_count, total_count); + + test_subprocesses_deinit(); + if (test_deinit_lib) lib_deinit(); + if (test_deinit_lib_signals) + lib_signals_deinit(); return failure_count == 0 ? 0 : 1; } @@ -385,22 +457,26 @@ static void test_run_funcs(void (*const test_functions[])(void)) { unsigned int i; + test_running = TRUE; for (i = 0; test_functions[i] != NULL; i++) { T_BEGIN { test_functions[i](); } T_END; } + test_running = FALSE; } static void test_run_named_funcs(const struct named_test tests[], const char *match) { unsigned int i; + test_running = TRUE; for (i = 0; tests[i].func != NULL; i++) { if (strstr(tests[i].name, match) != NULL) T_BEGIN { tests[i].func(); } T_END; } + test_running = FALSE; } static void run_one_fatal(test_fatal_func_t *fatal_function) @@ -438,21 +514,25 @@ static void test_run_fatals(test_fatal_func_t *const fatal_functions[]) { unsigned int i; + test_running = TRUE; for (i = 0; fatal_functions[i] != NULL; i++) { T_BEGIN { run_one_fatal(fatal_functions[i]); } T_END; } + test_running = FALSE; } static void test_run_named_fatals(const struct named_fatal fatals[], const char *match) { unsigned int i; + test_running = TRUE; for (i = 0; fatals[i].func != NULL; i++) { if (strstr(fatals[i].name, match) != NULL) T_BEGIN { run_one_fatal(fatals[i].func); } T_END; } + test_running = FALSE; } int test_run(void (*const test_functions[])(void)) @@ -494,7 +574,8 @@ void test_forked_end(void) i_free_and_null(expected_error_str); i_free_and_null(expected_fatal_str); i_free_and_null(test_prefix); - t_pop_last_unsafe(); /* as we were within a T_BEGIN { tests[i].func(); } T_END */ + if (test_running) + t_pop_last_unsafe(); /* as we were within a T_BEGIN { tests[i].func(); } T_END */ } void ATTR_NORETURN @@ -503,7 +584,8 @@ test_exit(int status) i_free_and_null(expected_error_str); i_free_and_null(expected_fatal_str); i_free_and_null(test_prefix); - t_pop_last_unsafe(); /* as we were within a T_BEGIN { tests[i].func(); } T_END */ + if (test_running) + t_pop_last_unsafe(); /* as we were within a T_BEGIN { tests[i].func(); } T_END */ lib_deinit(); lib_exit(status); } @@ -520,3 +602,8 @@ int test_create_temp_fd(void) i_unlink(str_c(str)); return fd; } + +void test_set_cleanup_callback(void (*callback)(void)) +{ + test_cleanup_callback = callback; +} diff --git a/src/lib-test/test-common.h b/src/lib-test/test-common.h index 2f06d67b13..fadae3b382 100644 --- a/src/lib-test/test-common.h +++ b/src/lib-test/test-common.h @@ -146,6 +146,8 @@ void test_out_quiet(const char *name, bool success); void test_out_reason_quiet(const char *name, bool success, const char *reason) ATTR_NULL(3); +void test_init(void); + int test_run(void (*const test_functions[])(void)) ATTR_WARN_UNUSED_RESULT; struct named_test { const char *name; @@ -204,4 +206,8 @@ void test_exit(int status) ATTR_NORETURN; function handles failures by calling i_fatal(). */ int test_create_temp_fd(void); +/* Set a cleanup callback that is executed even when the test program crashes or + exit()s unexpectedly. Note that this may be run in signal context. */ +void test_set_cleanup_callback(void (*callback)(void)); + #endif diff --git a/src/lib-test/test-private.h b/src/lib-test/test-private.h new file mode 100644 index 0000000000..c56e2cd3d4 --- /dev/null +++ b/src/lib-test/test-private.h @@ -0,0 +1,15 @@ +#ifndef TEST_PRIVATE_H +#define TEST_PRIVATE_H + +#include "test-common.h" + +void test_init_signals(void); + +void test_dir_cleanup(void); +void test_dir_deinit(void); +void test_dir_deinit_forked(void); + +void test_subprocess_cleanup(void); +void test_subprocesses_deinit(void); + +#endif diff --git a/src/lib-test/test-subprocess.c b/src/lib-test/test-subprocess.c index 8562cc2cc6..7e8ce087e3 100644 --- a/src/lib-test/test-subprocess.c +++ b/src/lib-test/test-subprocess.c @@ -8,6 +8,7 @@ #include "sleep.h" #include "time-util.h" #include "test-common.h" +#include "test-private.h" #include "test-subprocess.h" #include @@ -18,12 +19,10 @@ struct test_subprocess { pid_t pid; }; -volatile sig_atomic_t test_subprocess_is_child = 0; -static bool test_subprocess_lib_init = FALSE; +volatile sig_atomic_t test_subprocess_child_mark = 0; static volatile bool test_subprocess_notification_signal_received[SIGUSR1 + 1]; static struct event *test_subprocess_event = NULL; static ARRAY(struct test_subprocess *) test_subprocesses = ARRAY_INIT; -static void (*test_subprocess_cleanup_callback)(void) = NULL; static void test_subprocess_notification_signal(const siginfo_t *si, void *context); @@ -38,6 +37,8 @@ static void test_subprocess_free_all(void) { struct test_subprocess *subp; + if (!array_is_created(&test_subprocesses)) + return; array_foreach_elem(&test_subprocesses, subp) i_free(subp); array_free(&test_subprocesses); @@ -85,8 +86,8 @@ test_subprocess_child(int (*func)(void *context), void *context, } #undef test_subprocess_fork -void test_subprocess_fork(int (*func)(void *context), void *context, - bool continue_test) +pid_t test_subprocess_fork(int (*func)(void *context), void *context, + bool continue_test) { struct test_subprocess *subprocess; @@ -96,7 +97,7 @@ void test_subprocess_fork(int (*func)(void *context), void *context, /* avoid races: fork the child process with test_subprocess_is_child set to 1 in case it immediately receives a signal. */ - test_subprocess_is_child = 1; + test_subprocess_child_mark = 1; if ((subprocess->pid = fork()) == (pid_t)-1) i_fatal("test: sub-process: fork() failed: %m"); if (subprocess->pid == 0) { @@ -104,15 +105,18 @@ void test_subprocess_fork(int (*func)(void *context), void *context, * kill of PID 0, so just free it here explicitly. */ i_free(subprocess); io_loop_recreate(current_ioloop); + + test_subprocess_child_mark = 1; test_subprocess_free_all(); test_subprocess_child(func, context, continue_test); i_unreached(); } - test_subprocess_is_child = 0; + test_subprocess_child_mark = 0; array_push_back(&test_subprocesses, &subprocess); lib_signals_ioloop_attach(); + return subprocess->pid; } static void test_subprocess_verify_exit_status(int status) @@ -301,15 +305,13 @@ static void test_subprocess_kill_all_forced(void) * Main */ -volatile sig_atomic_t terminating = 0; - -static void test_subprocess_cleanup(void) +bool test_subprocess_is_child(void) { - if (test_subprocess_is_child != 0) { - /* Child processes must not execute the cleanups */ - return; - } + return (test_subprocess_child_mark != 0); +} +void test_subprocess_cleanup(void) +{ /* We get here when the test ended normally, badly failed, crashed, terminated, or executed exit() unexpectedly. The cleanups performed here are important and must be executed at all times. */ @@ -318,10 +320,6 @@ static void test_subprocess_cleanup(void) child processes will not be handled so well. So, we need to make sure here that we don't leave any pesky child processes alive. */ test_subprocess_kill_all_forced(); - - /* Perform any additional important cleanup specific to the test. */ - if (test_subprocess_cleanup_callback != NULL) - test_subprocess_cleanup_callback(); } static void @@ -335,38 +333,6 @@ test_subprocess_alarm(const siginfo_t *si ATTR_UNUSED, */ } -static void -test_subprocess_terminate(const siginfo_t *si, void *context ATTR_UNUSED) -{ - int signo = si->si_signo; - - if (terminating != 0) - raise(signo); - terminating = 1; - - /* Perform important cleanups */ - test_subprocess_cleanup(); - - (void)signal(signo, SIG_DFL); - if (signo == SIGTERM) - _exit(0); - else - raise(signo); -} - -static void test_atexit(void) -{ - /* NOTICE: This is also called by children, so be careful. */ - - /* Perform important cleanups */ - test_subprocess_cleanup(); -} - -void test_subprocess_set_cleanup_callback(void (*callback)(void)) -{ - test_subprocess_cleanup_callback = callback; -} - void test_subprocess_notify_signal_send(int signo, pid_t pid) { if (kill(pid, signo) < 0) @@ -417,24 +383,13 @@ test_subprocess_notification_signal(const siginfo_t *si, void test_subprocesses_init(bool debug) { - if (!lib_is_initialized()) { - lib_init(); - test_subprocess_lib_init = TRUE; - } - lib_signals_init(); - - atexit(test_atexit); - lib_signals_ignore(SIGPIPE, TRUE); - lib_signals_set_handler(SIGALRM, 0, test_subprocess_alarm, NULL); - lib_signals_set_handler(SIGTERM, 0, test_subprocess_terminate, NULL); - lib_signals_set_handler(SIGQUIT, 0, test_subprocess_terminate, NULL); - lib_signals_set_handler(SIGINT, 0, test_subprocess_terminate, NULL); - lib_signals_set_handler(SIGSEGV, 0, test_subprocess_terminate, NULL); - lib_signals_set_handler(SIGABRT, 0, test_subprocess_terminate, NULL); + test_init(); + test_init_signals(); lib_signals_set_handler(SIGHUP, LIBSIG_FLAG_RESTART, test_subprocess_notification_signal, NULL); lib_signals_set_handler(SIGUSR1, LIBSIG_FLAG_RESTART, test_subprocess_notification_signal, NULL); + lib_signals_set_handler(SIGALRM, 0, test_subprocess_alarm, NULL); i_array_init(&test_subprocesses, 8); @@ -450,8 +405,4 @@ void test_subprocesses_deinit(void) array_free(&test_subprocesses); event_unref(&test_subprocess_event); - lib_signals_deinit(); - - if (test_subprocess_lib_init) - lib_deinit(); } diff --git a/src/lib-test/test-subprocess.h b/src/lib-test/test-subprocess.h index 9a072924e9..edfccbadc4 100644 --- a/src/lib-test/test-subprocess.h +++ b/src/lib-test/test-subprocess.h @@ -3,15 +3,14 @@ #define TEST_SIGNALS_DEFAULT_TIMEOUT_MS 10000 -struct test_subprocess; - /* Fork a sub-process for this test. The func is the main function for the forked sub-process. The provided context is passed to the provided function. When continue_test=FALSE, the test is ended immediately in the sub-process, otherwise, the test continues and its result is used to set the exit code when the process ends gracefully. */ -void test_subprocess_fork(int (*func)(void *), void *context, - bool continue_test); +pid_t ATTR_NOWARN_UNUSED_RESULT +test_subprocess_fork(int (*func)(void *), void *context, + bool continue_test); #define test_subprocess_fork(func, context, continue_test) \ test_subprocess_fork( \ (int(*)(void*))func, \ @@ -27,9 +26,7 @@ void test_subprocess_wait_all(unsigned int timeout_secs); timeout. */ void test_subprocess_kill_all(unsigned int timeout_secs); -/* Set a cleanup callback that is executed even when the test program crashes or - exit()s unexpectedly. Note that this may be run in signal context. */ -void test_subprocess_set_cleanup_callback(void (*callback)(void)); +bool test_subprocess_is_child(void); /* Send a notification signal (SIGHUP) to the given PID */ void test_subprocess_notify_signal_send(int signo, pid_t pid); @@ -46,6 +43,5 @@ void test_subprocess_notify_signal_reset(int signo); void test_subprocess_notify_signal_wait(int signo, unsigned int timeout_msecs); void test_subprocesses_init(bool debug); -void test_subprocesses_deinit(void); #endif