test: intercept and collect valgrind errors
authorPeter Hutterer <peter.hutterer@who-t.net>
Tue, 15 Oct 2024 07:05:06 +0000 (17:05 +1000)
committerMarge Bot <emma+marge@anholt.net>
Wed, 30 Oct 2024 23:20:42 +0000 (23:20 +0000)
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: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1067>

meson.build
test/litest-runner.c

index 8511976c92bd80d3bd2ec144def77fbc1dcd45fe..dd28f8439f2e8cdbe3ce2263f4d8fb36b6a87fb9 100644 (file)
@@ -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',
index 03f5c6b0d90d065ba2adac47e6ecd0576057a519..5bf2c2d0f4ec81a9952f8ce3641d5ce9f4eb398f 100644 (file)
@@ -34,6 +34,7 @@
 #include <fcntl.h>
 #include <stdlib.h>
 #include <signal.h>
+#include <valgrind/valgrind.h>
 
 #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 */