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>
Patrick Steinhardt committedSep 10, 2025 at 15:09 UTCe7f04f651ac4550db3572720027503617d62ffeb
index 12d4af114f..125db05bc1 100644--- a/t/unit-tests/clar/CMakeLists.txt+++ b/t/unit-tests/clar/CMakeLists.txt@@ -1,8 +1,15 @@+include(CheckFunctionExists)+ cmake_minimum_required(VERSION 3.16..3.29) project(clar LANGUAGES C)-option(BUILD_TESTS "Build test executable" ON)+option(BUILD_EXAMPLE "Build the example." ON)++check_function_exists(realpath CLAR_HAS_REALPATH)+if(CLAR_HAS_REALPATH)+ add_compile_definitions(-DCLAR_HAS_REALPATH)+endif() add_library(clar INTERFACE) target_sources(clar INTERFACE@@ -25,4 +32,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(BUILD_TESTING) add_subdirectory(test) endif()++ if(BUILD_EXAMPLE)+ add_subdirectory(example)+ endif() endif()
t/unit-tests/clar/README.md
+20-17
index a8961c5f10..41595989ca 100644--- a/t/unit-tests/clar/README.md+++ b/t/unit-tests/clar/README.md@@ -26,8 +26,7 @@ Can you count to funk? ~~~~ sh $ mkdir tests $ cp -r $CLAR_ROOT/clar* tests- $ cp $CLAR_ROOT/test/clar_test.h tests- $ cp $CLAR_ROOT/test/main.c.sample tests/main.c+ $ cp $CLAR_ROOT/example/*.c tests ~~~~ - **One: Write some tests**@@ -147,7 +146,7 @@ To use Clar: 1. copy the Clar boilerplate to your test directory 2. copy (and probably modify) the sample `main.c` (from- `$CLAR_PATH/test/main.c.sample`)+ `$CLAR_PATH/example/main.c`) 3. run the Clar mixer (a.k.a. `generate.py`) to scan your test directory and write out the test suite metadata. 4. compile your test files and the Clar boilerplate into a single test@@ -159,7 +158,7 @@ The Clar boilerplate gives you a set of useful test assertions and features the `clar.c` and `clar.h` files, plus the code in the `clar/` subdirectory. You should not need to edit these files.-The sample `main.c` (i.e. `$CLAR_PATH/test/main.c.sample`) file invokes+The sample `main.c` (i.e. `$CLAR_PATH/example/main.c`) file invokes `clar_test(argc, argv)` to run the tests. Usually, you will edit this file to perform any framework specific initialization and teardown that you need.@@ -251,11 +250,16 @@ suite. - `cl_fixture(const char *)`: Gets the full path to a fixture file.-Please do note that these methods are *always* available whilst running a-test, even when calling auxiliary/static functions inside the same file.+### Auxiliary / helper functions-It's strongly encouraged to perform test assertions in auxiliary methods,-instead of returning error values. This is considered good Clar style.+The clar API is always available while running a test, even when calling+"auxiliary" (helper) functions.++You're encouraged to perform test assertions in those auxiliary+methods, instead of returning error values. This is considered good+Clar style. _However_, when you do this, you need to call `cl_invoke`+to preserve the current state; this ensures that failures are reported+as coming from the actual test, instead of the auxiliary method. Style Example:@@ -310,20 +314,19 @@ static void check_string(const char *str) void test_example__a_test_with_auxiliary_methods(void) {- check_string("foo");- check_string("bar");+ cl_invoke(check_string("foo"));+ cl_invoke(check_string("bar")); } ~~~~ About Clar ==========-Clar has been written from scratch by [Vicent Martí](https://github.com/vmg),-to replace the old testing framework in [libgit2][libgit2].--Do you know what languages are *in* on the SF startup scene? Node.js *and*-Latin. Follow [@vmg](https://www.twitter.com/vmg) on Twitter to-receive more lessons on word etymology. You can be hip too.-+Clar was originally written by [Vicent Martí](https://github.com/vmg),+to replace the old testing framework in [libgit2][libgit2]. It is+currently maintained by [Edward Thomson](https://github.com/ethomson),+and used by the [libgit2][libgit2] and [git][git] projects, amongst+others. [libgit2]: https://github.com/libgit2/libgit2+[git]: https://github.com/git/git
similarity index 96%rename from t/unit-tests/clar/test/main.c.samplerename to t/unit-tests/clar/example/main.cindex a4d91b72fa..f8def7fa6e 100644--- a/t/unit-tests/clar/test/main.c.sample+++ b/t/unit-tests/clar/example/main.c@@ -5,7 +5,7 @@ * For full terms see the included COPYING file. */-#include "clar_test.h"+#include "clar.h" /* * Minimal main() for clar tests.
t/unit-tests/clar/test/CMakeLists.txt
+27-12
index 7f2c1dc17a..96abd6ed93 100644--- a/t/unit-tests/clar/test/CMakeLists.txt+++ b/t/unit-tests/clar/test/CMakeLists.txt@@ -1,13 +1,15 @@+add_subdirectory(selftest_suite)+ find_package(Python COMPONENTS Interpreter REQUIRED) add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/clar.suite" COMMAND "${Python_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/generate.py" --output "${CMAKE_CURRENT_BINARY_DIR}"- DEPENDS main.c sample.c clar_test.h+ DEPENDS main.c selftest.c WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" )-add_executable(clar_test)-set_target_properties(clar_test PROPERTIES+add_executable(selftest)+set_target_properties(selftest PROPERTIES C_STANDARD 90 C_STANDARD_REQUIRED ON C_EXTENSIONS OFF@@ -15,25 +17,38 @@ set_target_properties(clar_test PROPERTIES # MSVC generates all kinds of warnings. We may want to fix these in the future # and then unconditionally treat warnings as errors.-if(NOT MSVC)- set_target_properties(clar_test PROPERTIES+if (NOT MSVC)+ set_target_properties(selftest PROPERTIES COMPILE_WARNING_AS_ERROR ON ) endif()-target_sources(clar_test PRIVATE+target_sources(selftest PRIVATE main.c- sample.c+ selftest.c "${CMAKE_CURRENT_BINARY_DIR}/clar.suite" )-target_compile_definitions(clar_test PRIVATE- CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/"+target_compile_definitions(selftest PRIVATE+ CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/expected/" )-target_compile_options(clar_test PRIVATE+target_compile_options(selftest PRIVATE $<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall> )-target_include_directories(clar_test PRIVATE+target_include_directories(selftest PRIVATE "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" )-target_link_libraries(clar_test clar)+target_link_libraries(selftest clar)++add_test(NAME build_selftest_suite+ COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --target selftest_suite+)+set_tests_properties(build_selftest_suite PROPERTIES FIXTURES_SETUP clar_test_fixture)++add_test(NAME build_selftest+ COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --target selftest+)+set_tests_properties(build_selftest PROPERTIES FIXTURES_SETUP clar_test_fixture)++add_test(NAME selftest COMMAND "${CMAKE_CURRENT_BINARY_DIR}/selftest" "$<TARGET_FILE:selftest_suite>")+set_tests_properties(selftest PROPERTIES FIXTURES_REQUIRED clar_test_fixture)
t/unit-tests/clar/test/clar_test.h
-16
deleted file mode 100644index 0fcaa639aa..0000000000--- a/t/unit-tests/clar/test/clar_test.h+++ /dev/null@@ -1,16 +0,0 @@-/*- * Copyright (c) Vicent Marti. All rights reserved.- *- * This file is part of clar, distributed under the ISC license.- * For full terms see the included COPYING file.- */-#ifndef __CLAR_TEST__-#define __CLAR_TEST__--/* Import the standard clar helper functions */-#include "clar.h"--/* Your custom shared includes / defines here */-extern int global_test_counter;--#endif
t/unit-tests/clar/test/expected/help
+12
new file mode 100644index 0000000000..4b2be69f97--- /dev/null+++ b/t/unit-tests/clar/test/expected/help@@ -0,0 +1,12 @@+Usage: selftest [options]++Options:+ -sname Run only the suite with `name` (can go to individual test name)+ -iname Include the suite with `name`+ -xname Exclude the suite with `name`+ -v Increase verbosity (show suite names)+ -q Decrease verbosity, inverse to -v+ -Q Quit as soon as a test fails+ -t Display results in tap format+ -l Print suite names+ -r[filename] Write summary file (to the optional filename)
t/unit-tests/clar/test/expected/quiet
+49
new file mode 100644index 0000000000..975164147f--- /dev/null+++ b/t/unit-tests/clar/test/expected/quiet@@ -0,0 +1,49 @@+ 1) Failure:+selftest::suite::1 [file:42]+ Function call failed: -1++ 2) Failure:+selftest::suite::2 [file:42]+ Expression is not true: 100 == 101++ 3) Failure:+selftest::suite::strings [file:42]+ String mismatch: "mismatched" != actual ("this one fails")+ 'mismatched' != 'expected' (at byte 0)++ 4) Failure:+selftest::suite::strings_with_length [file:42]+ String mismatch: "exactly" != actual ("this one fails")+ 'exa' != 'exp' (at byte 2)++ 5) Failure:+selftest::suite::int [file:42]+ 101 != value ("extra note on failing test")+ 101 != 100++ 6) Failure:+selftest::suite::int_fmt [file:42]+ 022 != value+ 0022 != 0144++ 7) Failure:+selftest::suite::bool [file:42]+ 0 != value+ 0 != 1++ 8) Failure:+selftest::suite::ptr [file:42]+ Pointer mismatch: p1 != p2+ 0x1 != 0x2++ 9) Failure:+selftest::suite::multiline_description [file:42]+ Function call failed: -1+ description line 1+ description line 2++ 10) Failure:+selftest::suite::null_string [file:42]+ String mismatch: "expected" != actual ("this one fails")+ 'expected' != NULL+
new file mode 100644index 0000000000..1156ade0f9--- /dev/null+++ b/t/unit-tests/clar/test/expected/stop_on_failure@@ -0,0 +1,8 @@+Loaded 1 suites: +Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')+F++ 1) Failure:+selftest::suite::1 [file:42]+ Function call failed: -1+
t/unit-tests/clar/test/expected/suite_names
+2
new file mode 100644index 0000000000..1b0f6397eb--- /dev/null+++ b/t/unit-tests/clar/test/expected/suite_names@@ -0,0 +1,2 @@+Test suites (use -s<name> to run just one):+ 0: selftest::suite
new file mode 100644index 0000000000..5984502773--- /dev/null+++ b/t/unit-tests/clar/test/expected/summary_without_filename@@ -0,0 +1,54 @@+Loaded 1 suites: +Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')+FFFFFFFFFF++ 1) Failure:+selftest::suite::1 [file:42]+ Function call failed: -1++ 2) Failure:+selftest::suite::2 [file:42]+ Expression is not true: 100 == 101++ 3) Failure:+selftest::suite::strings [file:42]+ String mismatch: "mismatched" != actual ("this one fails")+ 'mismatched' != 'expected' (at byte 0)++ 4) Failure:+selftest::suite::strings_with_length [file:42]+ String mismatch: "exactly" != actual ("this one fails")+ 'exa' != 'exp' (at byte 2)++ 5) Failure:+selftest::suite::int [file:42]+ 101 != value ("extra note on failing test")+ 101 != 100++ 6) Failure:+selftest::suite::int_fmt [file:42]+ 022 != value+ 0022 != 0144++ 7) Failure:+selftest::suite::bool [file:42]+ 0 != value+ 0 != 1++ 8) Failure:+selftest::suite::ptr [file:42]+ Pointer mismatch: p1 != p2+ 0x1 != 0x2++ 9) Failure:+selftest::suite::multiline_description [file:42]+ Function call failed: -1+ description line 1+ description line 2++ 10) Failure:+selftest::suite::null_string [file:42]+ String mismatch: "expected" != actual ("this one fails")+ 'expected' != NULL++written summary file to summary.xml
t/unit-tests/clar/test/expected/tap
+102
new file mode 100644index 0000000000..3dc4973dfa--- /dev/null+++ b/t/unit-tests/clar/test/expected/tap@@ -0,0 +1,102 @@+TAP version 13+# start of suite 1: selftest::suite+not ok 1 - selftest::suite::1+ ---+ reason: |+ Function call failed: -1+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 2 - selftest::suite::2+ ---+ reason: |+ Expression is not true: 100 == 101+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 3 - selftest::suite::strings+ ---+ reason: |+ String mismatch: "mismatched" != actual ("this one fails")+ 'mismatched' != 'expected' (at byte 0)+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 4 - selftest::suite::strings_with_length+ ---+ reason: |+ String mismatch: "exactly" != actual ("this one fails")+ 'exa' != 'exp' (at byte 2)+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 5 - selftest::suite::int+ ---+ reason: |+ 101 != value ("extra note on failing test")+ 101 != 100+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 6 - selftest::suite::int_fmt+ ---+ reason: |+ 022 != value+ 0022 != 0144+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 7 - selftest::suite::bool+ ---+ reason: |+ 0 != value+ 0 != 1+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 8 - selftest::suite::ptr+ ---+ reason: |+ Pointer mismatch: p1 != p2+ 0x1 != 0x2+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 9 - selftest::suite::multiline_description+ ---+ reason: |+ Function call failed: -1+ description line 1+ description line 2+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+not ok 10 - selftest::suite::null_string+ ---+ reason: |+ String mismatch: "expected" != actual ("this one fails")+ 'expected' != NULL+ at:+ file: 'file'+ line: 42+ function: 'func'+ ---+1..10
t/unit-tests/clar/test/expected/without_arguments
+53
new file mode 100644index 0000000000..08b67b874c--- /dev/null+++ b/t/unit-tests/clar/test/expected/without_arguments@@ -0,0 +1,53 @@+Loaded 1 suites: +Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')+FFFFFFFFFF++ 1) Failure:+selftest::suite::1 [file:42]+ Function call failed: -1++ 2) Failure:+selftest::suite::2 [file:42]+ Expression is not true: 100 == 101++ 3) Failure:+selftest::suite::strings [file:42]+ String mismatch: "mismatched" != actual ("this one fails")+ 'mismatched' != 'expected' (at byte 0)++ 4) Failure:+selftest::suite::strings_with_length [file:42]+ String mismatch: "exactly" != actual ("this one fails")+ 'exa' != 'exp' (at byte 2)++ 5) Failure:+selftest::suite::int [file:42]+ 101 != value ("extra note on failing test")+ 101 != 100++ 6) Failure:+selftest::suite::int_fmt [file:42]+ 022 != value+ 0022 != 0144++ 7) Failure:+selftest::suite::bool [file:42]+ 0 != value+ 0 != 1++ 8) Failure:+selftest::suite::ptr [file:42]+ Pointer mismatch: p1 != p2+ 0x1 != 0x2++ 9) Failure:+selftest::suite::multiline_description [file:42]+ Function call failed: -1+ description line 1+ description line 2++ 10) Failure:+selftest::suite::null_string [file:42]+ String mismatch: "expected" != actual ("this one fails")+ 'expected' != NULL+
t/unit-tests/clar/test/main.c
+13-28
index 59e56ad255..b1ba2996f1 100644--- a/t/unit-tests/clar/test/main.c+++ b/t/unit-tests/clar/test/main.c@@ -1,23 +1,9 @@-/*- * Copyright (c) Vicent Marti. All rights reserved.- *- * This file is part of clar, distributed under the ISC license.- * For full terms see the included COPYING file.- */+#include <stdio.h>+#include <string.h>-#include "clar_test.h"+#include "selftest.h"-/*- * Sample main() for clar tests.- *- * You should write your own main routine for clar tests that does specific- * setup and teardown as necessary for your application. The only required- * line is the call to `clar_test(argc, argv)`, which will execute the test- * suite. If you want to check the return value of the test application,- * your main() should return the same value returned by clar_test().- */--int global_test_counter = 0;+const char *selftest_binary_path; #ifdef _WIN32 int __cdecl main(int argc, char *argv[])@@ -25,16 +11,15 @@ int __cdecl main(int argc, char *argv[]) int main(int argc, char *argv[]) #endif {- int ret;-- /* Your custom initialization here */- global_test_counter = 0;-- /* Run the test suite */- ret = clar_test(argc, argv);+ if (argc < 2) {+ fprintf(stderr, "usage: %s <selftest-suite-executable> <options>\n",+ argv[0]);+ exit(1);+ }- /* Your custom cleanup here */- cl_assert_equal_i(8, global_test_counter);+ selftest_binary_path = argv[1];+ memmove(argv + 1, argv + 2, argc - 1);+ argc -= 1;- return ret;+ return clar_test(argc, argv); }
t/unit-tests/clar/test/selftest.c
+289
new file mode 100644index 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));+}
new file mode 100644index 0000000000..9597d6711a--- /dev/null+++ b/t/unit-tests/clar/test/selftest_suite/CMakeLists.txt@@ -0,0 +1,40 @@+find_package(Python COMPONENTS Interpreter REQUIRED)++add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"+ COMMAND "${Python_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/generate.py" --output "${CMAKE_CURRENT_BINARY_DIR}"+ DEPENDS main.c selftest_suite.c+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"+)++add_executable(selftest_suite)+set_target_properties(selftest_suite PROPERTIES+ C_STANDARD 90+ C_STANDARD_REQUIRED ON+ C_EXTENSIONS OFF+)++# MSVC generates all kinds of warnings. We may want to fix these in the future+# and then unconditionally treat warnings as errors.+if(NOT MSVC)+ set_target_properties(selftest_suite PROPERTIES+ COMPILE_WARNING_AS_ERROR ON+ )+endif()++target_sources(selftest_suite PRIVATE+ main.c+ selftest_suite.c+ "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"+)+target_compile_definitions(selftest_suite PRIVATE+ CLAR_FIXTURE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/"+ CLAR_SELFTEST+)+target_compile_options(selftest_suite PRIVATE+ $<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>+)+target_include_directories(selftest_suite PRIVATE+ "${CMAKE_SOURCE_DIR}"+ "${CMAKE_CURRENT_BINARY_DIR}"+)+target_link_libraries(selftest_suite clar)
t/unit-tests/clar/test/selftest_suite/main.c
+27
new file mode 100644index 0000000000..3ab581d390--- /dev/null+++ b/t/unit-tests/clar/test/selftest_suite/main.c@@ -0,0 +1,27 @@+/*+ * Copyright (c) Vicent Marti. All rights reserved.+ *+ * This file is part of clar, distributed under the ISC license.+ * For full terms see the included COPYING file.+ */++#include "clar.h"++/*+ * Selftest main() for clar tests.+ *+ * You should write your own main routine for clar tests that does specific+ * setup and teardown as necessary for your application. The only required+ * line is the call to `clar_test(argc, argv)`, which will execute the test+ * suite. If you want to check the return value of the test application,+ * your main() should return the same value returned by clar_test().+ */++#ifdef _WIN32+int __cdecl main(int argc, char *argv[])+#else+int main(int argc, char *argv[])+#endif+{+ return clar_test(argc, argv);+}