mirror of
https://github.com/git/git.git
synced 2026-01-11 13:23:12 +09:00
Update clar to commit 39f11fe (Merge pull request #131 from pks-gitlab/pks-integer-double-evaluation, 2025-12-05). This commit includes the following changes relevant to Git: - There are now typesafe integer comparison functions. Furthermore, the range of comparison functions has been included to also have relative comparisons, like "greater than". - There is a new `cl_failf()` macro that allows the caller to specify an error message with formatting directives. - The TAP format has been fixed to correctly terminate YAML blocks with "...\n" instead of "---\n". Note that we already had a `cl_failf()` function declared in our own sources. This function is equivalent to the upstreamed function, so we can simply drop it now. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
371 lines
8.6 KiB
C
371 lines
8.6 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 char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs)
|
|
{
|
|
SECURITY_ATTRIBUTES security_attributes = { 0 };
|
|
PROCESS_INFORMATION process_info = { 0 };
|
|
STARTUPINFO startup_info = { 0 };
|
|
char binary_path[4096] = { 0 };
|
|
char cmdline[4096] = { 0 };
|
|
char *output = NULL;
|
|
HANDLE stdout_write;
|
|
HANDLE stdout_read;
|
|
DWORD exit_code;
|
|
size_t i;
|
|
|
|
snprintf(binary_path, sizeof(binary_path), "%s/%s_suite.exe",
|
|
selftest_suite_directory, suite);
|
|
|
|
/*
|
|
* Assemble command line arguments. In theory we'd have to properly
|
|
* quote them. In practice none of our tests actually care.
|
|
*/
|
|
snprintf(cmdline, sizeof(cmdline), suite);
|
|
for (i = 0; i < nargs; i++) {
|
|
size_t cmdline_len = strlen(cmdline);
|
|
const char *arg = args[i];
|
|
cl_assert(cmdline_len + strlen(arg) < sizeof(cmdline));
|
|
snprintf(cmdline + cmdline_len, sizeof(cmdline) - cmdline_len,
|
|
" %s", arg);
|
|
}
|
|
|
|
/*
|
|
* 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(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));
|
|
cl_assert_equal_i(exit_code, expected_error_code);
|
|
|
|
return output;
|
|
}
|
|
|
|
static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...)
|
|
{
|
|
char *expected_output = NULL;
|
|
char *output = NULL;
|
|
const char *args[16];
|
|
va_list ap;
|
|
size_t i;
|
|
|
|
va_start(ap, expected_error_code);
|
|
for (i = 0; ; i++) {
|
|
const char *arg = va_arg(ap, const char *);
|
|
if (!arg)
|
|
break;
|
|
cl_assert(i < sizeof(args) / sizeof(*args));
|
|
args[i] = arg;
|
|
}
|
|
va_end(ap);
|
|
|
|
output = execute(suite, expected_error_code, args, i);
|
|
expected_output = read_file(cl_fixture(expected_output_file));
|
|
cl_assert_equal_s(output, expected_output);
|
|
|
|
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 char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs)
|
|
{
|
|
int pipe_fds[2];
|
|
pid_t pid;
|
|
|
|
cl_must_pass(pipe(pipe_fds));
|
|
|
|
pid = fork();
|
|
if (!pid) {
|
|
const char *final_args[17] = { NULL };
|
|
char binary_path[4096];
|
|
size_t len = 0;
|
|
size_t i;
|
|
|
|
cl_assert(nargs < sizeof(final_args) / sizeof(*final_args));
|
|
final_args[0] = suite;
|
|
for (i = 0; i < nargs; i++)
|
|
final_args[i + 1] = args[i];
|
|
|
|
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);
|
|
|
|
cl_assert(len + strlen(selftest_suite_directory) < sizeof(binary_path));
|
|
strcpy(binary_path, selftest_suite_directory);
|
|
len += strlen(selftest_suite_directory);
|
|
|
|
cl_assert(len + 1 < sizeof(binary_path));
|
|
binary_path[len] = '/';
|
|
len += 1;
|
|
|
|
cl_assert(len + strlen(suite) < sizeof(binary_path));
|
|
strcpy(binary_path + len, suite);
|
|
len += strlen(suite);
|
|
|
|
cl_assert(len + strlen("_suite") < sizeof(binary_path));
|
|
strcpy(binary_path + len, "_suite");
|
|
len += strlen("_suite");
|
|
|
|
binary_path[len] = '\0';
|
|
|
|
execv(binary_path, (char **) final_args);
|
|
exit(1);
|
|
} else if (pid > 0) {
|
|
pid_t waited_pid;
|
|
char *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);
|
|
|
|
return output;
|
|
} else {
|
|
cl_fail("Fork failed.");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...)
|
|
{
|
|
char *expected_output, *output;
|
|
const char *args[16];
|
|
va_list ap;
|
|
size_t i;
|
|
|
|
va_start(ap, expected_error_code);
|
|
for (i = 0; ; i++) {
|
|
cl_assert(i < sizeof(args) / sizeof(*args));
|
|
args[i] = va_arg(ap, const char *);
|
|
if (!args[i])
|
|
break;
|
|
}
|
|
va_end(ap);
|
|
|
|
output = execute(suite, expected_error_code, args, i);
|
|
expected_output = read_file(cl_fixture(expected_output_file));
|
|
cl_assert_equal_s(output, expected_output);
|
|
|
|
free(expected_output);
|
|
free(output);
|
|
}
|
|
#endif
|
|
|
|
void test_selftest__help(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "help", 1, "-h", NULL));
|
|
}
|
|
|
|
void test_selftest__without_arguments(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "without_arguments", 15, NULL));
|
|
}
|
|
|
|
void test_selftest__specific_test(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "specific_test", 1, "-scombined::bool", NULL));
|
|
}
|
|
|
|
void test_selftest__stop_on_failure(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "stop_on_failure", 1, "-Q", NULL));
|
|
}
|
|
|
|
void test_selftest__quiet(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "quiet", 15, "-q", NULL));
|
|
}
|
|
|
|
void test_selftest__tap(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "tap", 15, "-t", NULL));
|
|
}
|
|
|
|
void test_selftest__suite_names(void)
|
|
{
|
|
cl_invoke(assert_output("combined", "suite_names", 0, "-l", NULL));
|
|
}
|
|
|
|
void test_selftest__summary_without_filename(void)
|
|
{
|
|
struct stat st;
|
|
cl_invoke(assert_output("combined", "summary_without_filename", 15, "-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(assert_output("combined", "summary_with_filename", 15, "-rdifferent.xml", NULL));
|
|
/* The summary contains timestamps, so we cannot verify its contents. */
|
|
cl_must_pass(stat("different.xml", &st));
|
|
}
|
|
|
|
void test_selftest__pointer_equal(void)
|
|
{
|
|
const char *args[] = {
|
|
"-spointer::equal",
|
|
"-t"
|
|
};
|
|
char *output = execute("pointer", 0, args, 2);
|
|
cl_assert_equal_s(output,
|
|
"TAP version 13\n"
|
|
"# start of suite 1: pointer\n"
|
|
"ok 1 - pointer::equal\n"
|
|
"1..1\n"
|
|
);
|
|
free(output);
|
|
}
|
|
|
|
void test_selftest__pointer_unequal(void)
|
|
{
|
|
const char *args[] = {
|
|
"-spointer::unequal",
|
|
};
|
|
char *output = execute("pointer", 1, args, 1);
|
|
cl_assert(output);
|
|
cl_assert(strstr(output, "Pointer mismatch: "));
|
|
free(output);
|
|
}
|