290 lines
6.4 KiB
C
290 lines
6.4 KiB
C
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "selftest.h"
|
|
|
|
#ifdef _WIN32
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
|
|
static char *read_full(HANDLE h, int is_pipe)
|
|
{
|
|
char *data = NULL;
|
|
size_t data_size = 0;
|
|
|
|
while (1) {
|
|
CHAR buf[4096];
|
|
DWORD bytes_read;
|
|
|
|
if (!ReadFile(h, buf, sizeof(buf), &bytes_read, NULL)) {
|
|
if (!is_pipe)
|
|
cl_fail("Failed reading file handle.");
|
|
cl_assert_equal_i(GetLastError(), ERROR_BROKEN_PIPE);
|
|
break;
|
|
}
|
|
if (!bytes_read)
|
|
break;
|
|
|
|
data = realloc(data, data_size + bytes_read);
|
|
cl_assert(data);
|
|
memcpy(data + data_size, buf, bytes_read);
|
|
data_size += bytes_read;
|
|
}
|
|
|
|
data = realloc(data, data_size + 1);
|
|
cl_assert(data);
|
|
data[data_size] = '\0';
|
|
|
|
while (strstr(data, "\r\n")) {
|
|
char *ptr = strstr(data, "\r\n");
|
|
memmove(ptr, ptr + 1, strlen(ptr));
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
static char *read_file(const char *path)
|
|
{
|
|
char *content;
|
|
HANDLE file;
|
|
|
|
file = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
cl_assert(file != INVALID_HANDLE_VALUE);
|
|
content = read_full(file, 0);
|
|
cl_assert_equal_b(1, CloseHandle(file));
|
|
|
|
return content;
|
|
}
|
|
|
|
static void run(const char *expected_output_file, int expected_error_code, ...)
|
|
{
|
|
SECURITY_ATTRIBUTES security_attributes = { 0 };
|
|
PROCESS_INFORMATION process_info = { 0 };
|
|
STARTUPINFO startup_info = { 0 };
|
|
char cmdline[4096] = { 0 };
|
|
char *expected_output = NULL;
|
|
char *output = NULL;
|
|
HANDLE stdout_write;
|
|
HANDLE stdout_read;
|
|
DWORD exit_code;
|
|
va_list ap;
|
|
|
|
/*
|
|
* Assemble command line arguments. In theory we'd have to properly
|
|
* quote them. In practice none of our tests actually care.
|
|
*/
|
|
va_start(ap, expected_error_code);
|
|
snprintf(cmdline, sizeof(cmdline), "selftest");
|
|
while (1) {
|
|
size_t cmdline_len = strlen(cmdline);
|
|
const char *arg;
|
|
|
|
arg = va_arg(ap, const char *);
|
|
if (!arg)
|
|
break;
|
|
|
|
cl_assert(cmdline_len + strlen(arg) < sizeof(cmdline));
|
|
snprintf(cmdline + cmdline_len, sizeof(cmdline) - cmdline_len,
|
|
" %s", arg);
|
|
}
|
|
va_end(ap);
|
|
|
|
/*
|
|
* Create a pipe that we will use to read data from the child process.
|
|
* The writing side needs to be inheritable such that the child can use
|
|
* it as stdout and stderr. The reading side should only be used by the
|
|
* parent.
|
|
*/
|
|
security_attributes.nLength = sizeof(security_attributes);
|
|
security_attributes.bInheritHandle = TRUE;
|
|
cl_assert_equal_b(1, CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0));
|
|
cl_assert_equal_b(1, SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0));
|
|
|
|
/*
|
|
* Create the child process with our pipe.
|
|
*/
|
|
startup_info.cb = sizeof(startup_info);
|
|
startup_info.hStdError = stdout_write;
|
|
startup_info.hStdOutput = stdout_write;
|
|
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
|
cl_assert_equal_b(1, CreateProcess(selftest_binary_path, cmdline, NULL, NULL, TRUE,
|
|
0, NULL, NULL, &startup_info, &process_info));
|
|
cl_assert_equal_b(1, CloseHandle(stdout_write));
|
|
|
|
output = read_full(stdout_read, 1);
|
|
cl_assert_equal_b(1, CloseHandle(stdout_read));
|
|
cl_assert_equal_b(1, GetExitCodeProcess(process_info.hProcess, &exit_code));
|
|
|
|
expected_output = read_file(cl_fixture(expected_output_file));
|
|
cl_assert_equal_s(output, expected_output);
|
|
cl_assert_equal_i(exit_code, expected_error_code);
|
|
|
|
free(expected_output);
|
|
free(output);
|
|
}
|
|
|
|
#else
|
|
# include <errno.h>
|
|
# include <fcntl.h>
|
|
# include <limits.h>
|
|
# include <unistd.h>
|
|
# include <sys/wait.h>
|
|
|
|
static char *read_full(int fd)
|
|
{
|
|
size_t data_bytes = 0;
|
|
char *data = NULL;
|
|
|
|
while (1) {
|
|
char buf[4096];
|
|
ssize_t n;
|
|
|
|
n = read(fd, buf, sizeof(buf));
|
|
if (n < 0) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
cl_fail("Failed reading from child process.");
|
|
}
|
|
if (!n)
|
|
break;
|
|
|
|
data = realloc(data, data_bytes + n);
|
|
cl_assert(data);
|
|
|
|
memcpy(data + data_bytes, buf, n);
|
|
data_bytes += n;
|
|
}
|
|
|
|
data = realloc(data, data_bytes + 1);
|
|
cl_assert(data);
|
|
data[data_bytes] = '\0';
|
|
|
|
return data;
|
|
}
|
|
|
|
static char *read_file(const char *path)
|
|
{
|
|
char *data;
|
|
int fd;
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0)
|
|
cl_fail("Failed reading expected file.");
|
|
|
|
data = read_full(fd);
|
|
cl_must_pass(close(fd));
|
|
|
|
return data;
|
|
}
|
|
|
|
static void run(const char *expected_output_file, int expected_error_code, ...)
|
|
{
|
|
const char *argv[16];
|
|
int pipe_fds[2];
|
|
va_list ap;
|
|
pid_t pid;
|
|
int i;
|
|
|
|
va_start(ap, expected_error_code);
|
|
argv[0] = "selftest";
|
|
for (i = 1; ; i++) {
|
|
cl_assert(i < sizeof(argv) / sizeof(*argv));
|
|
|
|
argv[i] = va_arg(ap, const char *);
|
|
if (!argv[i])
|
|
break;
|
|
}
|
|
va_end(ap);
|
|
|
|
cl_must_pass(pipe(pipe_fds));
|
|
|
|
pid = fork();
|
|
if (!pid) {
|
|
if (dup2(pipe_fds[1], STDOUT_FILENO) < 0 ||
|
|
dup2(pipe_fds[1], STDERR_FILENO) < 0 ||
|
|
close(0) < 0 ||
|
|
close(pipe_fds[0]) < 0 ||
|
|
close(pipe_fds[1]) < 0)
|
|
exit(1);
|
|
|
|
execv(selftest_binary_path, (char **) argv);
|
|
exit(1);
|
|
} else if (pid > 0) {
|
|
pid_t waited_pid;
|
|
char *expected_output, *output;
|
|
int stat;
|
|
|
|
cl_must_pass(close(pipe_fds[1]));
|
|
|
|
output = read_full(pipe_fds[0]);
|
|
|
|
waited_pid = waitpid(pid, &stat, 0);
|
|
cl_assert_equal_i(pid, waited_pid);
|
|
cl_assert(WIFEXITED(stat));
|
|
cl_assert_equal_i(WEXITSTATUS(stat), expected_error_code);
|
|
|
|
expected_output = read_file(cl_fixture(expected_output_file));
|
|
cl_assert_equal_s(output, expected_output);
|
|
|
|
free(expected_output);
|
|
free(output);
|
|
} else {
|
|
cl_fail("Fork failed.");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void test_selftest__help(void)
|
|
{
|
|
cl_invoke(run("help", 1, "-h", NULL));
|
|
}
|
|
|
|
void test_selftest__without_arguments(void)
|
|
{
|
|
cl_invoke(run("without_arguments", 10, NULL));
|
|
}
|
|
|
|
void test_selftest__specific_test(void)
|
|
{
|
|
cl_invoke(run("specific_test", 1, "-sselftest::suite::bool", NULL));
|
|
}
|
|
|
|
void test_selftest__stop_on_failure(void)
|
|
{
|
|
cl_invoke(run("stop_on_failure", 1, "-Q", NULL));
|
|
}
|
|
|
|
void test_selftest__quiet(void)
|
|
{
|
|
cl_invoke(run("quiet", 10, "-q", NULL));
|
|
}
|
|
|
|
void test_selftest__tap(void)
|
|
{
|
|
cl_invoke(run("tap", 10, "-t", NULL));
|
|
}
|
|
|
|
void test_selftest__suite_names(void)
|
|
{
|
|
cl_invoke(run("suite_names", 0, "-l", NULL));
|
|
}
|
|
|
|
void test_selftest__summary_without_filename(void)
|
|
{
|
|
struct stat st;
|
|
cl_invoke(run("summary_without_filename", 10, "-r", NULL));
|
|
/* The summary contains timestamps, so we cannot verify its contents. */
|
|
cl_must_pass(stat("summary.xml", &st));
|
|
}
|
|
|
|
void test_selftest__summary_with_filename(void)
|
|
{
|
|
struct stat st;
|
|
cl_invoke(run("summary_with_filename", 10, "-rdifferent.xml", NULL));
|
|
/* The summary contains timestamps, so we cannot verify its contents. */
|
|
cl_must_pass(stat("different.xml", &st));
|
|
}
|