summaryrefslogtreecommitdiff
path: root/t/unit-tests/clar/test/selftest.c
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2025-09-10 15:09:58 +0200
committerJunio C Hamano <gitster@pobox.com>2025-09-11 09:08:49 -0700
commite7f04f651ac4550db3572720027503617d62ffeb (patch)
tree70f5f92f816b046ec3d2199997f1fbdce18657d7 /t/unit-tests/clar/test/selftest.c
parentc44beea485f0f2feaf460e2ac87fdd5608d63cf0 (diff)
t/unit-tests: update clar to fcbed04
Update clar to fcbed04 (Merge pull request #123 from pks-gitlab/pks-sandbox-ubsan, 2025-09-10). The most significant changes since the last version include: - Fixed platform support for HP-UX. - Fixes for how clar handles the `-q` flag. - A couple of leak fixes for reported clar errors. - A new `cl_invoke()` function that retains line information. - New infrastructure to create temporary directories. - Improved printing of error messages so that all lines are now properly indented. - Proper selftests for the clar. Most of these changes are somewhat irrelevant to us, but neither do we have to adjust to any of these changes, either. What _is_ interesting to us though is especially the fixed support for HP-UX, and eventually we may also want to use `cl_invoke()`. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 't/unit-tests/clar/test/selftest.c')
-rw-r--r--t/unit-tests/clar/test/selftest.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/t/unit-tests/clar/test/selftest.c b/t/unit-tests/clar/test/selftest.c
new file mode 100644
index 0000000000..abd585f4e4
--- /dev/null
+++ b/t/unit-tests/clar/test/selftest.c
@@ -0,0 +1,289 @@
+#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));
+}