2024-10-12 10:31:42 +10:00
|
|
|
/*
|
|
|
|
|
* Copyright © 2024 Red Hat, Inc.
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
|
|
|
* to deal in the Software without restriction, including without limitation
|
|
|
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
|
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
|
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice (including the next
|
|
|
|
|
* paragraph) shall be included in all copies or substantial portions of the
|
|
|
|
|
* Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
|
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
|
|
|
* DEALINGS IN THE SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <sys/epoll.h>
|
|
|
|
|
#include <sys/timerfd.h>
|
|
|
|
|
#include <sys/sysinfo.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#ifdef HAVE_PIDFD_OPEN
|
|
|
|
|
#include <sys/syscall.h>
|
|
|
|
|
#endif
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <signal.h>
|
2024-10-17 08:29:22 +10:00
|
|
|
#include <setjmp.h>
|
2024-10-15 17:05:06 +10:00
|
|
|
#include <valgrind/valgrind.h>
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
#include "litest-runner.h"
|
|
|
|
|
|
|
|
|
|
#include "util-files.h"
|
|
|
|
|
#include "util-list.h"
|
|
|
|
|
#include "util-stringbuf.h"
|
|
|
|
|
|
2024-10-17 08:29:22 +10:00
|
|
|
static bool use_jmpbuf; /* only used for max_forks = 0 */
|
|
|
|
|
static jmp_buf jmpbuf;
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
/* musl doesn't have this one but it's not that important */
|
|
|
|
|
#ifndef HAVE_SIGABBREV_NP
|
|
|
|
|
#define sigabbrev_np(...) "???"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static struct litest_runner *global_runner = NULL;
|
|
|
|
|
|
|
|
|
|
enum litest_runner_logfds {
|
|
|
|
|
FD_STDOUT,
|
|
|
|
|
FD_STDERR,
|
|
|
|
|
FD_LOG,
|
2024-10-15 17:05:06 +10:00
|
|
|
FD_VALGRIND,
|
2024-10-12 10:31:42 +10:00
|
|
|
_FD_LAST,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct litest_runner_test {
|
|
|
|
|
struct litest_runner_test_description desc;
|
|
|
|
|
struct list node;
|
|
|
|
|
|
|
|
|
|
enum litest_runner_result result;
|
|
|
|
|
int sig_or_errno;
|
|
|
|
|
|
|
|
|
|
struct stringbuf logs[_FD_LAST];
|
|
|
|
|
pid_t pid; /* the test's PID, if any */
|
|
|
|
|
int read_fds[_FD_LAST]; /* logging fds while the test is running */
|
|
|
|
|
|
|
|
|
|
int epollfd;
|
|
|
|
|
int pidfd;
|
|
|
|
|
int timerfd;
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
uint64_t start_millis;
|
|
|
|
|
uint64_t end_millis;
|
|
|
|
|
} times;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct litest_runner {
|
|
|
|
|
size_t max_forks;
|
|
|
|
|
unsigned int timeout;
|
|
|
|
|
bool verbose;
|
2024-10-15 15:28:56 +10:00
|
|
|
bool exit_on_fail;
|
2024-10-21 12:12:45 +10:00
|
|
|
FILE *fp;
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
int terminating;
|
|
|
|
|
|
|
|
|
|
struct list tests; /* struct litest_runner_test */
|
|
|
|
|
struct list tests_running; /* struct litest_runner_test */
|
|
|
|
|
struct list tests_complete; /* struct litest_runner_test */
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
time_t start;
|
|
|
|
|
time_t end;
|
|
|
|
|
uint64_t start_millis;
|
|
|
|
|
} times;
|
2024-10-16 18:31:35 +10:00
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
litest_runner_global_setup_func_t setup;
|
|
|
|
|
litest_runner_global_teardown_func_t teardown;
|
|
|
|
|
void *userdata;
|
|
|
|
|
} global;
|
2024-10-12 10:31:42 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A global variable that the tests can use
|
|
|
|
|
* to write log data to. Defaults to stdout
|
|
|
|
|
* but if used by the tests it shows up as separate
|
|
|
|
|
* from stdout in the logs.
|
|
|
|
|
*/
|
|
|
|
|
int testlog_fd = STDOUT_FILENO;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
close_pipes(int fds[_FD_LAST])
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < _FD_LAST; i++) {
|
|
|
|
|
fsync(fds[i]);
|
|
|
|
|
xclose(&fds[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
init_pipes(int read_fds[_FD_LAST], int write_fds[_FD_LAST])
|
|
|
|
|
{
|
|
|
|
|
int r;
|
|
|
|
|
int i;
|
|
|
|
|
int pipe_max_size = 4194304;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < _FD_LAST; i++) {
|
|
|
|
|
read_fds[i] = -1;
|
|
|
|
|
write_fds[i] = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef __linux__
|
|
|
|
|
{
|
|
|
|
|
FILE *f;
|
|
|
|
|
f = fopen("/proc/sys/fs/pipe-max-size", "re");
|
|
|
|
|
if (f) {
|
|
|
|
|
if (fscanf(f, "%d", &r) == 1)
|
|
|
|
|
pipe_max_size = min(r, pipe_max_size);
|
|
|
|
|
fclose(f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < _FD_LAST; i++) {
|
|
|
|
|
int pipe[2];
|
|
|
|
|
|
|
|
|
|
r = pipe2(pipe, O_CLOEXEC | O_NONBLOCK);
|
|
|
|
|
if (r < 0)
|
|
|
|
|
goto error;
|
|
|
|
|
read_fds[i] = pipe[0];
|
|
|
|
|
write_fds[i] = pipe[1];
|
|
|
|
|
#ifdef __linux__
|
|
|
|
|
/* Max pipe buffers, to avoid scrambling if reading lags.
|
|
|
|
|
* Can't use blocking write fds, since reading too slow
|
|
|
|
|
* then affects execution.
|
|
|
|
|
*/
|
|
|
|
|
fcntl(write_fds[i], F_SETPIPE_SZ, pipe_max_size);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
error:
|
|
|
|
|
r = -errno;
|
|
|
|
|
close_pipes(read_fds);
|
|
|
|
|
close_pipes(write_fds);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
litest_runner_result_as_str(enum litest_runner_result result)
|
|
|
|
|
{
|
|
|
|
|
switch (result) {
|
|
|
|
|
CASE_RETURN_STRING(LITEST_PASS);
|
|
|
|
|
CASE_RETURN_STRING(LITEST_NOT_APPLICABLE);
|
|
|
|
|
CASE_RETURN_STRING(LITEST_FAIL);
|
|
|
|
|
CASE_RETURN_STRING(LITEST_SYSTEM_ERROR);
|
|
|
|
|
CASE_RETURN_STRING(LITEST_TIMEOUT);
|
|
|
|
|
CASE_RETURN_STRING(LITEST_SKIP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
litest_abort_msg("Unknown result %d", result);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
litest_runner_test_close(struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
for (size_t i = 0; i < ARRAY_LENGTH(t->read_fds); i++) {
|
|
|
|
|
xclose(&t->read_fds[i]);
|
|
|
|
|
}
|
|
|
|
|
xclose(&t->epollfd);
|
|
|
|
|
xclose(&t->pidfd);
|
|
|
|
|
xclose(&t->timerfd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
litest_runner_test_destroy(struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
list_remove(&t->node);
|
|
|
|
|
close_pipes(t->read_fds);
|
|
|
|
|
if (t->pid != 0) {
|
|
|
|
|
kill(t->pid, SIGTERM);
|
|
|
|
|
t->pid = 0;
|
|
|
|
|
}
|
|
|
|
|
litest_runner_test_close(t);
|
|
|
|
|
for (int i = 0; i < _FD_LAST; i++) {
|
|
|
|
|
stringbuf_reset(&t->logs[i]);
|
|
|
|
|
}
|
|
|
|
|
free(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
litest_runner_detach_tests(struct litest_runner *runner)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner_test *t;
|
|
|
|
|
|
|
|
|
|
list_for_each_safe(t, &runner->tests, node) {
|
|
|
|
|
t->pid = 0;
|
|
|
|
|
}
|
|
|
|
|
list_for_each_safe(t, &runner->tests_complete, node) {
|
|
|
|
|
t->pid = 0;
|
|
|
|
|
}
|
|
|
|
|
list_for_each_safe(t, &runner->tests_running, node) {
|
|
|
|
|
t->pid = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
litest_runner_destroy(struct litest_runner *runner)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner_test *t;
|
|
|
|
|
list_for_each_safe(t, &runner->tests, node) {
|
|
|
|
|
litest_runner_test_destroy(t);
|
|
|
|
|
}
|
|
|
|
|
list_for_each_safe(t, &runner->tests_complete, node) {
|
|
|
|
|
litest_runner_test_destroy(t);
|
|
|
|
|
}
|
|
|
|
|
list_for_each_safe(t, &runner->tests_running, node) {
|
|
|
|
|
litest_runner_test_destroy(t);
|
|
|
|
|
}
|
|
|
|
|
free(runner);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum litest_runner_result
|
|
|
|
|
litest_runner_test_run(const struct litest_runner_test_description *desc)
|
|
|
|
|
{
|
|
|
|
|
const struct litest_runner_test_env env = {
|
|
|
|
|
.rangeval = desc->rangeval,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (desc->setup)
|
|
|
|
|
desc->setup(desc);
|
|
|
|
|
|
|
|
|
|
enum litest_runner_result result = desc->func(&env);
|
|
|
|
|
|
|
|
|
|
if (desc->teardown)
|
|
|
|
|
desc->teardown(desc);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
sighandler_forked_child(int signal)
|
|
|
|
|
{
|
|
|
|
|
struct sigaction act;
|
|
|
|
|
sigemptyset(&act.sa_mask);
|
|
|
|
|
act.sa_flags = 0;
|
|
|
|
|
act.sa_handler = SIG_DFL;
|
|
|
|
|
sigaction(signal, &act, NULL);
|
|
|
|
|
|
|
|
|
|
/* abort() was probably called by litest_assert... which inserts
|
|
|
|
|
* the backtrace anyway - we only need to backtrace the other signals
|
|
|
|
|
*/
|
|
|
|
|
if (signal != SIGABRT)
|
|
|
|
|
litest_backtrace();
|
|
|
|
|
|
|
|
|
|
raise(signal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
litest_runner_fork_test(struct litest_runner *runner,
|
|
|
|
|
struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
pid_t pid;
|
|
|
|
|
struct sigaction act;
|
|
|
|
|
int write_fds[_FD_LAST];
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
r = init_pipes(t->read_fds, write_fds);
|
|
|
|
|
if (r < 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid = fork();
|
|
|
|
|
if (pid < 0) {
|
|
|
|
|
int r = -errno;
|
|
|
|
|
close_pipes(t->read_fds);
|
|
|
|
|
close_pipes(write_fds);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pid > 0) { /* parent */
|
|
|
|
|
close_pipes(write_fds);
|
|
|
|
|
t->pid = pid;
|
|
|
|
|
return pid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* child */
|
|
|
|
|
close_pipes(t->read_fds);
|
|
|
|
|
|
|
|
|
|
/* Catch any crashers so we can insert a backtrace */
|
|
|
|
|
sigemptyset(&act.sa_mask);
|
|
|
|
|
act.sa_flags = 0;
|
|
|
|
|
act.sa_handler = sighandler_forked_child;
|
|
|
|
|
sigaction(SIGSEGV, &act, NULL);
|
|
|
|
|
sigaction(SIGBUS, &act, NULL);
|
|
|
|
|
sigaction(SIGSEGV, &act, NULL);
|
|
|
|
|
sigaction(SIGABRT, &act, NULL);
|
|
|
|
|
/* SIGALARM is used for our timeout */
|
|
|
|
|
sigaction(SIGALRM, &act, NULL);
|
|
|
|
|
|
|
|
|
|
r = dup2(write_fds[FD_STDERR], STDERR_FILENO);
|
|
|
|
|
litest_assert_errno_success(r);
|
|
|
|
|
setlinebuf(stderr);
|
|
|
|
|
r = dup2(write_fds[FD_STDOUT], STDOUT_FILENO);
|
|
|
|
|
litest_assert_errno_success(r);
|
|
|
|
|
setlinebuf(stdout);
|
|
|
|
|
|
|
|
|
|
/* For convenience in the tests, let this be a global variable. */
|
|
|
|
|
testlog_fd = write_fds[FD_LOG];
|
|
|
|
|
|
|
|
|
|
/* We're forked so we have a full copy of everything - but all we want
|
|
|
|
|
* to do is avoid valgrind complaining about memleaks in the child
|
|
|
|
|
* process. So let's cleanup everything, copy the one thing we
|
|
|
|
|
* care about and proceed to the exit */
|
|
|
|
|
struct litest_runner_test_description desc = t->desc;
|
|
|
|
|
litest_runner_detach_tests(runner);
|
|
|
|
|
litest_runner_destroy(runner);
|
|
|
|
|
|
|
|
|
|
/* Now run the actual test */
|
|
|
|
|
enum litest_runner_result result = litest_runner_test_run(&desc);
|
|
|
|
|
|
|
|
|
|
close_pipes(write_fds);
|
|
|
|
|
|
|
|
|
|
exit(result);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 17:05:06 +10:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
static bool
|
|
|
|
|
litest_runner_test_collect_child(struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
int r;
|
|
|
|
|
int status;
|
|
|
|
|
|
|
|
|
|
r = waitpid(t->pid, &status, WNOHANG);
|
|
|
|
|
if (r <= 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
|
t->result = WEXITSTATUS(status);
|
2024-10-15 17:05:06 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2024-10-12 10:31:42 +10:00
|
|
|
switch (t->result) {
|
|
|
|
|
case LITEST_PASS:
|
|
|
|
|
case LITEST_SKIP:
|
|
|
|
|
case LITEST_NOT_APPLICABLE:
|
|
|
|
|
case LITEST_FAIL:
|
|
|
|
|
case LITEST_TIMEOUT:
|
|
|
|
|
case LITEST_SYSTEM_ERROR:
|
|
|
|
|
break;
|
|
|
|
|
/* if a test execve's itself allow for the normal
|
|
|
|
|
* exit codes to map to the results */
|
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wswitch"
|
|
|
|
|
case 0:
|
|
|
|
|
t->result = LITEST_PASS;
|
|
|
|
|
break;
|
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
default: {
|
|
|
|
|
char msg[64];
|
|
|
|
|
snprintf(msg, sizeof(msg), "Invalid test exit status %d", t->result);
|
|
|
|
|
stringbuf_append_string(&t->logs[FD_LOG], msg);
|
|
|
|
|
t->result = LITEST_FAIL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-10-15 17:05:06 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2024-10-12 10:31:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t now = 0;
|
|
|
|
|
now_in_us(&now);
|
|
|
|
|
t->times.end_millis = us2ms(now);
|
|
|
|
|
|
2024-10-15 17:05:06 +10:00
|
|
|
if (RUNNING_ON_VALGRIND) {
|
|
|
|
|
char *filename = valgrind_logfile(t->pid);
|
|
|
|
|
collect_file(filename, &t->logs[FD_VALGRIND]);
|
|
|
|
|
free(filename);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
t->pid = 0;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
litest_runner_test_setup_monitoring(struct litest_runner *runner,
|
|
|
|
|
struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
int pidfd = -1, timerfd = -1, epollfd = -1;
|
|
|
|
|
struct epoll_event ev[10];
|
|
|
|
|
size_t nevents = 0;
|
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_PIDFD_OPEN
|
|
|
|
|
pidfd = syscall(SYS_pidfd_open, t->pid, 0);
|
|
|
|
|
#else
|
|
|
|
|
errno = ENOSYS;
|
|
|
|
|
#endif
|
|
|
|
|
/* If we don't have pidfd, we use a timerfd to ping us every 200ms */
|
|
|
|
|
if (pidfd < 0 && errno == ENOSYS) {
|
|
|
|
|
pidfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
|
|
|
if (pidfd == -1) {
|
|
|
|
|
r = -errno;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
r = timerfd_settime(pidfd, 0,
|
|
|
|
|
&((struct itimerspec ){
|
|
|
|
|
.it_interval.tv_nsec = 200 * 1000 * 1000,
|
|
|
|
|
.it_value.tv_nsec = 200 * 1000 * 1000,
|
|
|
|
|
}), NULL);
|
|
|
|
|
if (r < 0) {
|
|
|
|
|
r = -errno;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Each test has an epollfd with:
|
|
|
|
|
* - a timerfd so we can kill() it if it hangs
|
|
|
|
|
* - a pidfd so we get notified when the test exits
|
|
|
|
|
* - a pipe for stdout and a pipe for stderr
|
|
|
|
|
* - a pipe for logging (the various pwtest functions)
|
|
|
|
|
* - a pipe for the daemon's stdout
|
|
|
|
|
*/
|
|
|
|
|
timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
|
|
|
if (timerfd < 0) {
|
|
|
|
|
r = -errno;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
timerfd_settime(timerfd, 0, &((struct itimerspec ){ .it_value.tv_sec = runner->timeout}), NULL);
|
|
|
|
|
|
|
|
|
|
epollfd = epoll_create(1);
|
|
|
|
|
if (epollfd < 0)
|
|
|
|
|
goto error;
|
|
|
|
|
ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = pidfd };
|
|
|
|
|
ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = t->read_fds[FD_STDOUT] };
|
|
|
|
|
ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = t->read_fds[FD_STDERR] };
|
|
|
|
|
ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = t->read_fds[FD_LOG] };
|
|
|
|
|
ev[nevents++] = (struct epoll_event){ .events = EPOLLIN, .data.fd = timerfd };
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < nevents; i++) {
|
|
|
|
|
r = epoll_ctl(epollfd, EPOLL_CTL_ADD, ev[i].data.fd, &ev[i]);
|
|
|
|
|
if (r < 0) {
|
|
|
|
|
r = -errno;
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t->epollfd = epollfd;
|
|
|
|
|
t->pidfd = pidfd;
|
|
|
|
|
t->timerfd = timerfd;
|
|
|
|
|
|
|
|
|
|
r = 0;
|
|
|
|
|
|
|
|
|
|
error:
|
|
|
|
|
if (r) {
|
|
|
|
|
xclose(&pidfd);
|
|
|
|
|
xclose(&timerfd);
|
|
|
|
|
xclose(&epollfd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
litest_runner_test_check_status(struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
struct epoll_event e;
|
|
|
|
|
|
|
|
|
|
if (t->pid == 0) /* nofork case */
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
/* FIXME: this is a busy wait ... */
|
|
|
|
|
int r = epoll_wait(t->epollfd, &e, 1, 50);
|
|
|
|
|
if (r == 0)
|
|
|
|
|
return -EAGAIN;
|
|
|
|
|
|
|
|
|
|
if (r == -1) {
|
|
|
|
|
goto error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.data.fd == t->pidfd) {
|
|
|
|
|
uint64_t buf;
|
|
|
|
|
int ignore = read(t->pidfd, &buf, sizeof(buf)); /* for timerfd fallback */
|
|
|
|
|
(void)ignore;
|
|
|
|
|
if (litest_runner_test_collect_child(t)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (e.data.fd == t->timerfd) {
|
|
|
|
|
/* SIGALARM so we get the backtrace */
|
|
|
|
|
kill(t->pid, SIGALRM);
|
|
|
|
|
t->result = LITEST_TIMEOUT;
|
|
|
|
|
waitpid(t->pid, NULL, 0);
|
|
|
|
|
t->pid = 0;
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
for (int i = 0; i < _FD_LAST; i++) {
|
|
|
|
|
if (e.data.fd == t->read_fds[i]) {
|
|
|
|
|
stringbuf_append_from_fd(&t->logs[i], e.data.fd, 1024);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
|
error:
|
|
|
|
|
return -errno;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
litest_runner_test_update_errno(struct litest_runner_test *t, int error)
|
|
|
|
|
{
|
|
|
|
|
if (error)
|
|
|
|
|
t->sig_or_errno = -error;
|
|
|
|
|
|
|
|
|
|
if (error == SIGTERM) {
|
|
|
|
|
char msg[64];
|
|
|
|
|
snprintf(msg, sizeof(msg), "litest: tests terminated by signal\n");
|
|
|
|
|
stringbuf_append_string(&t->logs[FD_LOG], msg);
|
|
|
|
|
t->result = LITEST_SYSTEM_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ARRAY_LENGTH(t->read_fds); i++) {
|
|
|
|
|
stringbuf_append_from_fd(&t->logs[i], t->read_fds[i], 1024);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-17 08:29:22 +10:00
|
|
|
__attribute__((noreturn))
|
|
|
|
|
void
|
|
|
|
|
litest_runner_abort(void) {
|
|
|
|
|
if (use_jmpbuf) {
|
|
|
|
|
longjmp(jmpbuf, SIGABRT);
|
|
|
|
|
} else {
|
|
|
|
|
abort();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
static int
|
|
|
|
|
litest_runner_run_test(struct litest_runner *runner, struct litest_runner_test *t)
|
|
|
|
|
{
|
2024-10-17 08:29:22 +10:00
|
|
|
int r;
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
t->result = LITEST_SYSTEM_ERROR;
|
|
|
|
|
|
|
|
|
|
uint64_t now = 0;
|
|
|
|
|
now_in_us(&now);
|
|
|
|
|
t->times.start_millis = us2ms(now);
|
|
|
|
|
|
|
|
|
|
if (runner->max_forks == 0) {
|
2024-10-17 08:29:22 +10:00
|
|
|
if (use_jmpbuf && setjmp(jmpbuf) == 0) {
|
|
|
|
|
t->result = litest_runner_test_run(&t->desc);
|
|
|
|
|
} else {
|
|
|
|
|
t->result = LITEST_FAIL;
|
|
|
|
|
}
|
|
|
|
|
r = 0; /* -Wclobbered */
|
2024-10-12 10:31:42 +10:00
|
|
|
} else {
|
|
|
|
|
r = litest_runner_fork_test(runner, t);
|
|
|
|
|
if (r >= 0)
|
|
|
|
|
r = litest_runner_test_setup_monitoring(runner, t);
|
|
|
|
|
litest_runner_test_update_errno(t, -r);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (r >= 0) {
|
|
|
|
|
list_remove(&t->node);
|
|
|
|
|
list_append(&runner->tests_running, &t->node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
print_lines(FILE *fp, const char *log, const char *prefix)
|
|
|
|
|
{
|
|
|
|
|
size_t nlines = 0;
|
|
|
|
|
char **lines = strv_from_string (log, "\n", &nlines);
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < nlines; i++) {
|
|
|
|
|
fprintf(fp, "%s%s\n", prefix, lines[i]);
|
|
|
|
|
}
|
|
|
|
|
strv_free(lines);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
litest_runner_log_test_result(struct litest_runner *runner, struct litest_runner_test *t)
|
|
|
|
|
{
|
|
|
|
|
const char *color = NULL;
|
|
|
|
|
const char *status = NULL;
|
|
|
|
|
|
|
|
|
|
litest_assert_int_ge(t->result, (enum litest_runner_result)LITEST_PASS);
|
|
|
|
|
litest_assert_int_le(t->result, (enum litest_runner_result)LITEST_SYSTEM_ERROR);
|
|
|
|
|
|
|
|
|
|
switch (t->result) {
|
|
|
|
|
case LITEST_PASS: color = ANSI_BRIGHT_GREEN; break;
|
|
|
|
|
case LITEST_FAIL: color = ANSI_BRIGHT_RED; break;
|
|
|
|
|
case LITEST_SKIP: color = ANSI_BRIGHT_YELLOW; break;
|
|
|
|
|
case LITEST_NOT_APPLICABLE: color = ANSI_BLUE; break;
|
|
|
|
|
case LITEST_TIMEOUT: color = ANSI_BRIGHT_CYAN; break;
|
|
|
|
|
case LITEST_SYSTEM_ERROR: color = ANSI_BRIGHT_MAGENTA; break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " - name: \"%s\"\n", t->desc.name);
|
2024-10-12 10:31:42 +10:00
|
|
|
int min = t->desc.args.range.lower,
|
|
|
|
|
max = t->desc.args.range.upper;
|
|
|
|
|
if (range_is_valid(&t->desc.args.range))
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " rangeval: %d # %d..%d\n", t->desc.rangeval, min, max);
|
2024-10-12 10:31:42 +10:00
|
|
|
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp,
|
2024-10-12 10:31:42 +10:00
|
|
|
" duration: %ld # (ms), total test run time: %02d:%02d\n",
|
|
|
|
|
t->times.end_millis - t->times.start_millis,
|
|
|
|
|
(ms2s(t->times.end_millis - runner->times.start_millis)) / 60,
|
|
|
|
|
(ms2s(t->times.end_millis - runner->times.start_millis)) % 60);
|
|
|
|
|
|
|
|
|
|
status = litest_runner_result_as_str(t->result);
|
2024-10-21 12:12:45 +10:00
|
|
|
bool is_tty = isatty(fileno(runner->fp));
|
|
|
|
|
fprintf(runner->fp, " status: %s%s%s\n",
|
|
|
|
|
is_tty ? color : "",
|
2024-10-12 10:31:42 +10:00
|
|
|
&status[7], /* skip LITEST_ prefix */
|
2024-10-21 12:12:45 +10:00
|
|
|
is_tty ? ANSI_NORMAL : "");
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
switch (t->result) {
|
|
|
|
|
case LITEST_PASS:
|
|
|
|
|
case LITEST_SKIP:
|
|
|
|
|
case LITEST_NOT_APPLICABLE:
|
|
|
|
|
if (!runner->verbose)
|
|
|
|
|
return;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (t->sig_or_errno > 0)
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " signal: %d # SIG%s \n",
|
2024-10-12 10:31:42 +10:00
|
|
|
t->sig_or_errno,
|
|
|
|
|
sigabbrev_np(t->sig_or_errno));
|
|
|
|
|
else if (t->sig_or_errno < 0)
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " errno: %d # %s\n",
|
2024-10-12 10:31:42 +10:00
|
|
|
-t->sig_or_errno,
|
|
|
|
|
strerror(-t->sig_or_errno));
|
|
|
|
|
if (!stringbuf_is_empty(&t->logs[FD_LOG])) {
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " log: |\n");
|
|
|
|
|
print_lines(runner->fp, t->logs[FD_LOG].data, " ");
|
2024-10-12 10:31:42 +10:00
|
|
|
}
|
|
|
|
|
if (!stringbuf_is_empty(&t->logs[FD_STDOUT])) {
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " stdout: |\n");
|
|
|
|
|
print_lines(runner->fp, t->logs[FD_STDOUT].data, " ");
|
2024-10-12 10:31:42 +10:00
|
|
|
}
|
|
|
|
|
if (!stringbuf_is_empty(&t->logs[FD_STDERR])) {
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " stderr: |\n");
|
|
|
|
|
print_lines(runner->fp, t->logs[FD_STDERR].data, " ");
|
2024-10-12 10:31:42 +10:00
|
|
|
}
|
2024-10-15 17:05:06 +10:00
|
|
|
if (!stringbuf_is_empty(&t->logs[FD_VALGRIND])) {
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " valgrind: |\n");
|
|
|
|
|
print_lines(runner->fp, t->logs[FD_VALGRIND].data, " ");
|
2024-10-15 17:05:06 +10:00
|
|
|
}
|
2024-10-12 10:31:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct litest_runner *
|
|
|
|
|
litest_runner_new(void)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner *runner = zalloc(sizeof *runner);
|
|
|
|
|
|
|
|
|
|
list_init(&runner->tests);
|
|
|
|
|
list_init(&runner->tests_complete);
|
|
|
|
|
list_init(&runner->tests_running);
|
|
|
|
|
runner->timeout = LITEST_RUNNER_DEFAULT_TIMEOUT;
|
|
|
|
|
runner->max_forks = get_nprocs() * 2;
|
2024-10-21 12:12:45 +10:00
|
|
|
runner->fp = stderr;
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
return runner;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
litest_runner_set_timeout(struct litest_runner *runner,
|
|
|
|
|
unsigned int timeout)
|
|
|
|
|
{
|
|
|
|
|
runner->timeout = timeout;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 12:12:45 +10:00
|
|
|
void
|
|
|
|
|
litest_runner_set_output_file(struct litest_runner *runner,
|
|
|
|
|
FILE *fp)
|
|
|
|
|
{
|
|
|
|
|
setlinebuf(fp);
|
|
|
|
|
runner->fp = fp;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
void
|
|
|
|
|
litest_runner_set_num_parallel(struct litest_runner *runner,
|
|
|
|
|
size_t num_jobs)
|
|
|
|
|
{
|
|
|
|
|
runner->max_forks = num_jobs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
litest_runner_set_verbose(struct litest_runner *runner,
|
|
|
|
|
bool verbose)
|
|
|
|
|
{
|
|
|
|
|
runner->verbose = verbose;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 15:28:56 +10:00
|
|
|
void
|
|
|
|
|
litest_runner_set_exit_on_fail(struct litest_runner *runner, bool do_exit)
|
|
|
|
|
{
|
|
|
|
|
runner->exit_on_fail = do_exit;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 18:31:35 +10:00
|
|
|
void
|
|
|
|
|
litest_runner_set_setup_funcs(struct litest_runner *runner,
|
|
|
|
|
litest_runner_global_setup_func_t setup,
|
|
|
|
|
litest_runner_global_teardown_func_t teardown,
|
|
|
|
|
void *userdata)
|
|
|
|
|
{
|
|
|
|
|
runner->global.setup = setup;
|
|
|
|
|
runner->global.teardown = teardown;
|
|
|
|
|
runner->global.userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
void
|
|
|
|
|
litest_runner_add_test(struct litest_runner *runner,
|
|
|
|
|
const struct litest_runner_test_description *desc)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner_test *t = zalloc(sizeof(*t));
|
|
|
|
|
|
|
|
|
|
t->desc = *desc;
|
|
|
|
|
t->epollfd = -1;
|
|
|
|
|
t->pidfd = -1;
|
|
|
|
|
t->timerfd = -1;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < _FD_LAST; i++) {
|
|
|
|
|
stringbuf_init(&t->logs[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i < ARRAY_LENGTH(t->read_fds); i++) {
|
|
|
|
|
t->read_fds[i] = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list_append(&runner->tests, &t->node);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
litest_runner_check_finished_tests(struct litest_runner *runner)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner_test *running;
|
|
|
|
|
size_t count = 0;
|
|
|
|
|
|
|
|
|
|
list_for_each_safe(running, &runner->tests_running, node) {
|
|
|
|
|
int r = litest_runner_test_check_status(running);
|
|
|
|
|
if (r == -EAGAIN)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
uint64_t now = 0;
|
|
|
|
|
now_in_us(&now);
|
|
|
|
|
running->times.end_millis = us2ms(now);
|
|
|
|
|
|
|
|
|
|
if (r < 0)
|
|
|
|
|
litest_runner_test_update_errno(running, -r);
|
|
|
|
|
|
|
|
|
|
litest_runner_log_test_result(runner, running);
|
|
|
|
|
litest_runner_test_close(running);
|
|
|
|
|
list_remove(&running->node);
|
|
|
|
|
list_append(&runner->tests_complete, &running->node);
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
runner_sighandler(int sig)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner_test *t;
|
|
|
|
|
struct litest_runner *runner = global_runner;
|
|
|
|
|
|
|
|
|
|
list_for_each(t, &runner->tests_running, node) {
|
|
|
|
|
if (t->pid != 0) {
|
|
|
|
|
kill(t->pid, SIGTERM);
|
|
|
|
|
t->pid = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
global_runner->terminating = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
|
setup_sighandler(int sig)
|
|
|
|
|
{
|
|
|
|
|
struct sigaction act, oact;
|
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
|
|
sigemptyset(&act.sa_mask);
|
|
|
|
|
sigaddset(&act.sa_mask, sig);
|
|
|
|
|
act.sa_flags = 0;
|
|
|
|
|
act.sa_handler = runner_sighandler;
|
|
|
|
|
rc = sigaction(sig, &act, &oact);
|
|
|
|
|
litest_assert_int_ne(rc, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum litest_runner_result
|
|
|
|
|
litest_runner_run_tests(struct litest_runner *runner)
|
|
|
|
|
{
|
|
|
|
|
struct litest_runner_test *t;
|
|
|
|
|
size_t available_jobs = max(runner->max_forks, 1);
|
|
|
|
|
char timestamp[64];
|
|
|
|
|
struct tm *ltime;
|
|
|
|
|
|
|
|
|
|
global_runner = runner; /* sigh, need this for signal handling */
|
|
|
|
|
|
2024-10-16 18:31:35 +10:00
|
|
|
if (runner->global.setup)
|
|
|
|
|
runner->global.setup(runner->global.userdata);
|
|
|
|
|
|
2024-10-17 08:29:22 +10:00
|
|
|
use_jmpbuf = runner->max_forks == 0;
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
setup_sighandler(SIGINT);
|
|
|
|
|
|
|
|
|
|
uint64_t now = 0;
|
|
|
|
|
now_in_us(&now);
|
|
|
|
|
|
|
|
|
|
runner->times.start_millis = us2ms(now);
|
|
|
|
|
runner->times.start = time(NULL);
|
|
|
|
|
ltime = localtime(&runner->times.start);
|
|
|
|
|
strftime(timestamp, sizeof(timestamp), "%FT%H:%M", ltime);
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, "start: %ld # \"%s\"\n", runner->times.start, timestamp);
|
|
|
|
|
fprintf(runner->fp, "jobs: %zd\n", runner->max_forks);
|
|
|
|
|
fprintf(runner->fp, "tests:\n");
|
2024-10-12 10:31:42 +10:00
|
|
|
list_for_each_safe(t, &runner->tests, node) {
|
|
|
|
|
int r = litest_runner_run_test(runner, t);
|
|
|
|
|
if (r >= 0) {
|
|
|
|
|
available_jobs--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Wait for something to become available */
|
|
|
|
|
while (available_jobs == 0 && !runner->terminating) {
|
|
|
|
|
int complete = litest_runner_check_finished_tests(runner);
|
|
|
|
|
available_jobs += complete;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (runner->terminating) {
|
|
|
|
|
break;
|
2024-10-15 15:28:56 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (runner->exit_on_fail) {
|
|
|
|
|
bool do_exit = false;
|
|
|
|
|
struct litest_runner_test *complete;
|
|
|
|
|
list_for_each(complete, &runner->tests_complete, node) {
|
|
|
|
|
switch (complete->result) {
|
|
|
|
|
case LITEST_FAIL:
|
|
|
|
|
case LITEST_SYSTEM_ERROR:
|
|
|
|
|
case LITEST_TIMEOUT:
|
|
|
|
|
do_exit = true;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (do_exit)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (do_exit)
|
|
|
|
|
break;
|
2024-10-12 10:31:42 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (!runner->terminating && !list_empty(&runner->tests_running)) {
|
|
|
|
|
litest_runner_check_finished_tests(runner);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 18:31:35 +10:00
|
|
|
if (runner->global.teardown)
|
|
|
|
|
runner->global.teardown(runner->global.userdata);
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
size_t npass = 0, nfail = 0, nskip = 0, nna = 0;
|
|
|
|
|
size_t ncomplete = 0;
|
|
|
|
|
|
|
|
|
|
list_for_each(t, &runner->tests_complete, node) {
|
|
|
|
|
ncomplete++;
|
|
|
|
|
switch (t->result) {
|
|
|
|
|
case LITEST_PASS:
|
|
|
|
|
npass++;
|
|
|
|
|
break;
|
|
|
|
|
case LITEST_NOT_APPLICABLE:
|
|
|
|
|
nna++;
|
|
|
|
|
break;
|
|
|
|
|
case LITEST_FAIL:
|
|
|
|
|
case LITEST_SYSTEM_ERROR:
|
|
|
|
|
case LITEST_TIMEOUT:
|
|
|
|
|
nfail++;
|
|
|
|
|
break;
|
|
|
|
|
case LITEST_SKIP:
|
|
|
|
|
nskip++;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
runner->times.end = time(NULL);
|
|
|
|
|
ltime = localtime(&runner->times.end);
|
|
|
|
|
strftime(timestamp, sizeof(timestamp), "%FT%H:%M", ltime);
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, "end: %ld # \"%s\"\n", runner->times.end, timestamp);
|
|
|
|
|
fprintf(runner->fp,
|
2024-10-12 10:31:42 +10:00
|
|
|
"duration: %ld # (s) %02ld:%02ld\n",
|
|
|
|
|
runner->times.end - runner->times.start,
|
|
|
|
|
(runner->times.end - runner->times.start) / 60,
|
|
|
|
|
(runner->times.end - runner->times.start) % 60);
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, "summary:\n");
|
|
|
|
|
fprintf(runner->fp, " completed: %zd\n", ncomplete);
|
|
|
|
|
fprintf(runner->fp, " pass: %zd\n", npass);
|
|
|
|
|
fprintf(runner->fp, " na: %zd\n", nna);
|
|
|
|
|
fprintf(runner->fp, " fail: %zd\n", nfail);
|
|
|
|
|
fprintf(runner->fp, " skip: %zd\n", nskip);
|
2024-10-12 10:31:42 +10:00
|
|
|
if (nfail > 0) {
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " failed:\n");
|
2024-10-12 10:31:42 +10:00
|
|
|
list_for_each(t, &runner->tests_complete, node) {
|
|
|
|
|
switch (t->result) {
|
|
|
|
|
case LITEST_FAIL:
|
|
|
|
|
case LITEST_SYSTEM_ERROR:
|
|
|
|
|
case LITEST_TIMEOUT:
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " - \"%s\"\n", t->desc.name);
|
2024-10-12 10:31:42 +10:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 17:05:06 +10:00
|
|
|
if (RUNNING_ON_VALGRIND) {
|
|
|
|
|
char *filename = valgrind_logfile(getpid());
|
|
|
|
|
struct stringbuf *b = stringbuf_new();
|
|
|
|
|
|
|
|
|
|
collect_file(filename, b);
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, "valgrind:\n");
|
|
|
|
|
print_lines(runner->fp, b->data, " ");
|
|
|
|
|
fprintf(runner->fp, "# Valgrind log is incomplete, see %s for full log\n", filename);
|
2024-10-15 17:05:06 +10:00
|
|
|
free(filename);
|
|
|
|
|
stringbuf_destroy(b);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-12 10:31:42 +10:00
|
|
|
enum litest_runner_result result = LITEST_PASS;
|
|
|
|
|
|
|
|
|
|
/* Didn't finish */
|
|
|
|
|
if (!list_empty(&runner->tests) || !list_empty(&runner->tests_running)) {
|
|
|
|
|
result = LITEST_SYSTEM_ERROR;
|
|
|
|
|
} else {
|
|
|
|
|
list_for_each(t, &runner->tests_complete, node) {
|
|
|
|
|
switch (t->result) {
|
|
|
|
|
case LITEST_PASS:
|
|
|
|
|
case LITEST_NOT_APPLICABLE:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
result = LITEST_FAIL;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Status is always prefixed with LITEST_ */
|
2024-10-21 12:12:45 +10:00
|
|
|
fprintf(runner->fp, " status: %s\n", &litest_runner_result_as_str(result)[7]);
|
2024-10-12 10:31:42 +10:00
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|