From 16e5bdabac101ab2d0cc87b5fdbc84305690cc37 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 15 Oct 2024 17:05:06 +1000 Subject: [PATCH] test: intercept and collect valgrind errors This works because we always invoke with --exit-errorcode=3 from the test suite. It won't work for manual invocations but oh well. Part-of: --- meson.build | 1 + test/litest-runner.c | 74 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/meson.build b/meson.build index 8511976c..dd28f843 100644 --- a/meson.build +++ b/meson.build @@ -1001,6 +1001,7 @@ if get_option('tests') valgrind_suppressions_file = dir_src_test / 'valgrind.suppressions' add_test_setup('valgrind', exe_wrapper : [ valgrind, + '--log-file=valgrind.%p.log', '--leak-check=full', '--gen-suppressions=all', '--error-exitcode=3', diff --git a/test/litest-runner.c b/test/litest-runner.c index 03f5c6b0..5bf2c2d0 100644 --- a/test/litest-runner.c +++ b/test/litest-runner.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "litest-runner.h" @@ -52,6 +53,7 @@ enum litest_runner_logfds { FD_STDOUT, FD_STDERR, FD_LOG, + FD_VALGRIND, _FD_LAST, }; @@ -341,6 +343,35 @@ litest_runner_fork_test(struct litest_runner *runner, exit(result); } +static char * +valgrind_logfile(pid_t pid) +{ + const char *prefix = getenv("LITEST_VALGRIND_LOGDIR"); + if (!prefix) + prefix = "."; + + char *filename = NULL; + int rc = xasprintf(&filename, "%s/valgrind.%d.log", prefix, pid); + litest_assert_neg_errno_success(rc); + + return filename; +} + +static void +collect_file(const char *filename, struct stringbuf *b) +{ + int fd = open(filename, O_RDONLY); + if (fd == -1) { + char *msg; + xasprintf(&msg, "Failed to find '%s': %m", filename); + stringbuf_append_string(b, msg); + free(msg); + } else { + stringbuf_append_from_fd(b, fd, 0); + close(fd); + } +} + static bool litest_runner_test_collect_child(struct litest_runner_test *t) { @@ -351,10 +382,14 @@ litest_runner_test_collect_child(struct litest_runner_test *t) if (r <= 0) return false; - t->pid = 0; - if (WIFEXITED(status)) { t->result = WEXITSTATUS(status); + if (RUNNING_ON_VALGRIND && t->result == 3) { + char msg[64]; + snprintf(msg, sizeof(msg), "valgrind exited with an error code, see logs\n"); + stringbuf_append_string(&t->logs[FD_LOG], msg); + t->result = LITEST_SYSTEM_ERROR; + } switch (t->result) { case LITEST_PASS: case LITEST_SKIP: @@ -379,20 +414,25 @@ litest_runner_test_collect_child(struct litest_runner_test *t) break; } } - return true; - } - - if (WIFSIGNALED(status)) { - t->sig_or_errno = WTERMSIG(status); - t->result = (t->sig_or_errno == t->desc.args.signal) ? LITEST_PASS : LITEST_FAIL; } else { - t->result = LITEST_FAIL; + if (WIFSIGNALED(status)) { + t->sig_or_errno = WTERMSIG(status); + t->result = (t->sig_or_errno == t->desc.args.signal) ? LITEST_PASS : LITEST_FAIL; + } else { + t->result = LITEST_FAIL; + } } uint64_t now = 0; now_in_us(&now); t->times.end_millis = us2ms(now); + if (RUNNING_ON_VALGRIND) { + char *filename = valgrind_logfile(t->pid); + collect_file(filename, &t->logs[FD_VALGRIND]); + free(filename); + } + t->pid = 0; return true; @@ -648,6 +688,10 @@ litest_runner_log_test_result(struct litest_runner *runner, struct litest_runner fprintf(stderr, " stderr: |\n"); print_lines(stderr, t->logs[FD_STDERR].data, " "); } + if (!stringbuf_is_empty(&t->logs[FD_VALGRIND])) { + fprintf(stderr, " valgrind: |\n"); + print_lines(stderr, t->logs[FD_VALGRIND].data, " "); + } } struct litest_runner * @@ -889,6 +933,18 @@ litest_runner_run_tests(struct litest_runner *runner) } } + if (RUNNING_ON_VALGRIND) { + char *filename = valgrind_logfile(getpid()); + struct stringbuf *b = stringbuf_new(); + + collect_file(filename, b); + fprintf(stderr, "valgrind:\n"); + print_lines(stderr, b->data, " "); + fprintf(stderr, "# Valgrind log is incomplete, see %s for full log\n", filename); + free(filename); + stringbuf_destroy(b); + } + enum litest_runner_result result = LITEST_PASS; /* Didn't finish */ -- 2.34.1