{"thread":{"id":"64118","subject":"[PATCH] t/unit-tests: update clar to fcbed04","startedAt":"2025-09-10T13:10:08Z","lastAt":"2025-09-11T16:08:38Z","messageCount":2,"participants":["Patrick Steinhardt","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"526026","messageId":"20250910-b4-pks-clar-update-v1-1-26a196237e0a@pks.im","threadId":"64118","inReplyTo":null,"subject":"[PATCH] t/unit-tests: update clar to fcbed04","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-10T13:09:58Z","receivedAt":"2025-09-10T13:10:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Update clar to fcbed04 (Merge pull request #123 from\npks-gitlab/pks-sandbox-ubsan, 2025-09-10). The most significant changes\nsince the last version include:\n\n  - Fixed platform support for HP-UX.\n\n  - Fixes for how clar handles the `-q` flag.\n\n  - A couple of leak fixes for reported clar errors.\n\n  - A new `cl_invoke()` function that retains line information.\n\n  - New infrastructure to create temporary directories.\n\n  - Improved printing of error messages so that all lines are now\n    properly indented.\n\n  - Proper selftests for the clar.\n\nMost of these changes are somewhat irrelevant to us, but neither do we\nhave to adjust to any of these changes, either. What _is_ interesting to\nus though is especially the fixed support for HP-UX, and eventually we\nmay also want to use `cl_invoke()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\nHi,\n\nthis updates us to the latest version of clar. Most of the changes are\nsomewhat uninteresting to us, but this contains the fixes for HP-UX.\n\nJunio, I was wondering whether we also want to do this similarly to how\nyou handle the gitk/git-gui pull requests. I ultimately don't mind it\nmuch though, so I'm also happy to do this with \"normal\" patch series.\n\nThanks!\n\nPatrick\n---\n t/unit-tests/clar/.github/workflows/ci.yml         |  18 +-\n t/unit-tests/clar/CMakeLists.txt                   |  13 +-\n t/unit-tests/clar/README.md                        |  37 +--\n t/unit-tests/clar/clar.c                           | 149 ++++++++---\n t/unit-tests/clar/clar.h                           |  83 ++++--\n t/unit-tests/clar/clar/fixtures.h                  |   6 +-\n t/unit-tests/clar/clar/fs.h                        |  29 +--\n t/unit-tests/clar/clar/print.h                     |  60 +++--\n t/unit-tests/clar/clar/sandbox.h                   | 226 ++++++++++++----\n t/unit-tests/clar/clar/summary.h                   |   5 +-\n t/unit-tests/clar/example/CMakeLists.txt           |  28 ++\n t/unit-tests/clar/example/example.c                |   6 +\n .../clar/{test/main.c.sample => example/main.c}    |   2 +-\n t/unit-tests/clar/test/CMakeLists.txt              |  39 ++-\n t/unit-tests/clar/test/clar_test.h                 |  16 --\n t/unit-tests/clar/test/expected/help               |  12 +\n t/unit-tests/clar/test/expected/quiet              |  49 ++++\n t/unit-tests/clar/test/expected/specific_test      |   9 +\n t/unit-tests/clar/test/expected/stop_on_failure    |   8 +\n t/unit-tests/clar/test/expected/suite_names        |   2 +\n t/unit-tests/clar/test/expected/summary.xml        |  45 ++++\n .../clar/test/expected/summary_with_filename       |  54 ++++\n .../clar/test/expected/summary_without_filename    |  54 ++++\n t/unit-tests/clar/test/expected/tap                | 102 ++++++++\n t/unit-tests/clar/test/expected/without_arguments  |  53 ++++\n t/unit-tests/clar/test/main.c                      |  41 +--\n t/unit-tests/clar/test/selftest.c                  | 289 +++++++++++++++++++++\n t/unit-tests/clar/test/selftest.h                  |   3 +\n .../clar/test/selftest_suite/CMakeLists.txt        |  40 +++\n t/unit-tests/clar/test/selftest_suite/main.c       |  27 ++\n .../test/{ => selftest_suite}/resources/test/file  |   0\n .../{sample.c => selftest_suite/selftest_suite.c}  |  44 ++--\n 32 files changed, 1311 insertions(+), 238 deletions(-)\n\ndiff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml\nindex 0065843d17a..c41f55f6ff5 100644\n--- a/t/unit-tests/clar/.github/workflows/ci.yml\n+++ b/t/unit-tests/clar/.github/workflows/ci.yml\n@@ -13,6 +13,11 @@ jobs:\n         platform:\n           - os: ubuntu-latest\n             generator: Unix Makefiles\n+          - os: ubuntu-latest\n+            generator: Unix Makefiles\n+            env:\n+              CC: \"clang\"\n+              CFLAGS: \"-fsanitize=leak\"\n           - os: macos-latest\n             generator: Unix Makefiles\n           - os: windows-latest\n@@ -21,15 +26,26 @@ jobs:\n             generator: MSYS Makefiles\n           - os: windows-latest\n             generator: MinGW Makefiles\n+      fail-fast: false\n \n     runs-on: ${{ matrix.platform.os }}\n \n+    env:\n+      CC: ${{matrix.platform.env.CC}}\n+      CFLAGS: ${{matrix.platform.env.CFLAGS}}\n+\n     steps:\n     - name: Check out\n       uses: actions/checkout@v2\n     - name: Build\n+      shell: bash\n       run: |\n         mkdir build\n         cd build\n         cmake .. -G \"${{matrix.platform.generator}}\"\n-        cmake --build .\n+        cmake --build . --verbose\n+    - name: Test\n+      shell: bash\n+      run: |\n+        cd build\n+        CTEST_OUTPUT_ON_FAILURE=1 ctest --build-config Debug\ndiff --git a/t/unit-tests/clar/CMakeLists.txt b/t/unit-tests/clar/CMakeLists.txt\nindex 12d4af114fe..125db05bc10 100644\n--- a/t/unit-tests/clar/CMakeLists.txt\n+++ b/t/unit-tests/clar/CMakeLists.txt\n@@ -1,8 +1,15 @@\n+include(CheckFunctionExists)\n+\n cmake_minimum_required(VERSION 3.16..3.29)\n \n project(clar LANGUAGES C)\n \n-option(BUILD_TESTS \"Build test executable\" ON)\n+option(BUILD_EXAMPLE \"Build the example.\" ON)\n+\n+check_function_exists(realpath CLAR_HAS_REALPATH)\n+if(CLAR_HAS_REALPATH)\n+\tadd_compile_definitions(-DCLAR_HAS_REALPATH)\n+endif()\n \n add_library(clar INTERFACE)\n target_sources(clar INTERFACE\n@@ -25,4 +32,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)\n \tif(BUILD_TESTING)\n \t\tadd_subdirectory(test)\n \tendif()\n+\n+\tif(BUILD_EXAMPLE)\n+\t\tadd_subdirectory(example)\n+\tendif()\n endif()\ndiff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md\nindex a8961c5f10f..41595989ca6 100644\n--- a/t/unit-tests/clar/README.md\n+++ b/t/unit-tests/clar/README.md\n@@ -26,8 +26,7 @@ Can you count to funk?\n     ~~~~ sh\n     $ mkdir tests\n     $ cp -r $CLAR_ROOT/clar* tests\n-    $ cp $CLAR_ROOT/test/clar_test.h tests\n-    $ cp $CLAR_ROOT/test/main.c.sample tests/main.c\n+    $ cp $CLAR_ROOT/example/*.c tests\n     ~~~~\n \n - **One: Write some tests**\n@@ -147,7 +146,7 @@ To use Clar:\n \n 1. copy the Clar boilerplate to your test directory\n 2. copy (and probably modify) the sample `main.c` (from\n-   `$CLAR_PATH/test/main.c.sample`)\n+   `$CLAR_PATH/example/main.c`)\n 3. run the Clar mixer (a.k.a. `generate.py`) to scan your test directory and\n    write out the test suite metadata.\n 4. compile your test files and the Clar boilerplate into a single test\n@@ -159,7 +158,7 @@ The Clar boilerplate gives you a set of useful test assertions and features\n the `clar.c` and `clar.h` files, plus the code in the `clar/` subdirectory.\n You should not need to edit these files.\n \n-The sample `main.c` (i.e. `$CLAR_PATH/test/main.c.sample`) file invokes\n+The sample `main.c` (i.e. `$CLAR_PATH/example/main.c`) file invokes\n `clar_test(argc, argv)` to run the tests.  Usually, you will edit this file\n to perform any framework specific initialization and teardown that you need.\n \n@@ -251,11 +250,16 @@ suite.\n \n -   `cl_fixture(const char *)`: Gets the full path to a fixture file.\n \n-Please do note that these methods are *always* available whilst running a\n-test, even when calling auxiliary/static functions inside the same file.\n+### Auxiliary / helper functions\n \n-It's strongly encouraged to perform test assertions in auxiliary methods,\n-instead of returning error values. This is considered good Clar style.\n+The clar API is always available while running a test, even when calling\n+\"auxiliary\" (helper) functions.\n+\n+You're encouraged to perform test assertions in those auxiliary\n+methods, instead of returning error values. This is considered good\n+Clar style. _However_, when you do this, you need to call `cl_invoke`\n+to preserve the current state; this ensures that failures are reported\n+as coming from the actual test, instead of the auxiliary method.\n \n Style Example:\n \n@@ -310,20 +314,19 @@ static void check_string(const char *str)\n \n void test_example__a_test_with_auxiliary_methods(void)\n {\n-    check_string(\"foo\");\n-    check_string(\"bar\");\n+    cl_invoke(check_string(\"foo\"));\n+    cl_invoke(check_string(\"bar\"));\n }\n ~~~~\n \n About Clar\n ==========\n \n-Clar has been written from scratch by [Vicent Martí](https://github.com/vmg),\n-to replace the old testing framework in [libgit2][libgit2].\n-\n-Do you know what languages are *in* on the SF startup scene? Node.js *and*\n-Latin.  Follow [@vmg](https://www.twitter.com/vmg) on Twitter to\n-receive more lessons on word etymology. You can be hip too.\n-\n+Clar was originally written by [Vicent Martí](https://github.com/vmg),\n+to replace the old testing framework in [libgit2][libgit2]. It is\n+currently maintained by [Edward Thomson](https://github.com/ethomson),\n+and used by the [libgit2][libgit2] and [git][git] projects, amongst\n+others.\n \n [libgit2]: https://github.com/libgit2/libgit2\n+[git]: https://github.com/git/git\ndiff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c\nindex 03a3aa8e873..80c53594252 100644\n--- a/t/unit-tests/clar/clar.c\n+++ b/t/unit-tests/clar/clar.c\n@@ -79,6 +79,8 @@\n #\telse\n #\t\tdefine p_snprintf snprintf\n #\tendif\n+\n+#\tdefine localtime_r(timer, buf) (localtime_s(buf, timer) == 0 ? buf : NULL)\n #else\n #\tinclude <sys/wait.h> /* waitpid(2) */\n #\tinclude <unistd.h>\n@@ -150,7 +152,6 @@ static struct {\n \n \tenum cl_output_format output_format;\n \n-\tint report_errors_only;\n \tint exit_on_error;\n \tint verbosity;\n \n@@ -164,6 +165,10 @@ static struct {\n \tstruct clar_report *reports;\n \tstruct clar_report *last_report;\n \n+\tconst char *invoke_file;\n+\tconst char *invoke_func;\n+\tsize_t invoke_line;\n+\n \tvoid (*local_cleanup)(void *);\n \tvoid *local_cleanup_payload;\n \n@@ -199,8 +204,10 @@ static void clar_print_onabortv(const char *msg, va_list argp);\n static void clar_print_onabort(const char *msg, ...);\n \n /* From clar_sandbox.c */\n-static void clar_unsandbox(void);\n-static void clar_sandbox(void);\n+static void clar_tempdir_init(void);\n+static void clar_tempdir_shutdown(void);\n+static int clar_sandbox_create(const char *suite_name, const char *test_name);\n+static int clar_sandbox_cleanup(void);\n \n /* From summary.h */\n static struct clar_summary *clar_summary_init(const char *filename);\n@@ -304,6 +311,8 @@ clar_run_test(\n \n \tCL_TRACE(CL_TRACE__TEST__BEGIN);\n \n+\tclar_sandbox_create(suite->name, test->name);\n+\n \t_clar.last_report->start = time(NULL);\n \tclar_time_now(&start);\n \n@@ -328,9 +337,13 @@ clar_run_test(\n \tif (_clar.local_cleanup != NULL)\n \t\t_clar.local_cleanup(_clar.local_cleanup_payload);\n \n+\tclar__clear_invokepoint();\n+\n \tif (cleanup->ptr != NULL)\n \t\tcleanup->ptr();\n \n+\tclar_sandbox_cleanup();\n+\n \tCL_TRACE(CL_TRACE__TEST__END);\n \n \t_clar.tests_ran++;\n@@ -339,11 +352,7 @@ clar_run_test(\n \t_clar.local_cleanup = NULL;\n \t_clar.local_cleanup_payload = NULL;\n \n-\tif (_clar.report_errors_only) {\n-\t\tclar_report_errors(_clar.last_report);\n-\t} else {\n-\t\tclar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);\n-\t}\n+\tclar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);\n }\n \n static void\n@@ -360,8 +369,7 @@ clar_run_suite(const struct clar_suite *suite, const char *filter)\n \tif (_clar.exit_on_error && _clar.total_errors)\n \t\treturn;\n \n-\tif (!_clar.report_errors_only)\n-\t\tclar_print_onsuite(suite->name, ++_clar.suites_ran);\n+\tclar_print_onsuite(suite->name, ++_clar.suites_ran);\n \n \t_clar.active_suite = suite->name;\n \t_clar.active_test = NULL;\n@@ -428,12 +436,12 @@ clar_usage(const char *arg)\n \tprintf(\"  -iname        Include the suite with `name`\\n\");\n \tprintf(\"  -xname        Exclude the suite with `name`\\n\");\n \tprintf(\"  -v            Increase verbosity (show suite names)\\n\");\n-\tprintf(\"  -q            Only report tests that had an error\\n\");\n+\tprintf(\"  -q            Decrease verbosity, inverse to -v\\n\");\n \tprintf(\"  -Q            Quit as soon as a test fails\\n\");\n \tprintf(\"  -t            Display results in tap format\\n\");\n \tprintf(\"  -l            Print suite names\\n\");\n \tprintf(\"  -r[filename]  Write summary file (to the optional filename)\\n\");\n-\texit(-1);\n+\texit(1);\n }\n \n static void\n@@ -441,18 +449,11 @@ clar_parse_args(int argc, char **argv)\n {\n \tint i;\n \n-\t/* Verify options before execute */\n \tfor (i = 1; i < argc; ++i) {\n \t\tchar *argument = argv[i];\n \n-\t\tif (argument[0] != '-' || argument[1] == '\\0'\n-\t\t    || strchr(\"sixvqQtlr\", argument[1]) == NULL) {\n+\t\tif (argument[0] != '-' || argument[1] == '\\0')\n \t\t\tclar_usage(argv[0]);\n-\t\t}\n-\t}\n-\n-\tfor (i = 1; i < argc; ++i) {\n-\t\tchar *argument = argv[i];\n \n \t\tswitch (argument[1]) {\n \t\tcase 's':\n@@ -465,8 +466,13 @@ clar_parse_args(int argc, char **argv)\n \t\t\targument += offset;\n \t\t\targlen = strlen(argument);\n \n-\t\t\tif (arglen == 0)\n-\t\t\t\tclar_usage(argv[0]);\n+\t\t\tif (arglen == 0) {\n+\t\t\t\tif (i + 1 == argc)\n+\t\t\t\t\tclar_usage(argv[0]);\n+\n+\t\t\t\targument = argv[++i];\n+\t\t\t\targlen = strlen(argument);\n+\t\t\t}\n \n \t\t\tfor (j = 0; j < _clar_suite_count; ++j) {\n \t\t\t\tsuitelen = strlen(_clar_suites[j].name);\n@@ -483,9 +489,6 @@ clar_parse_args(int argc, char **argv)\n \n \t\t\t\t\t++found;\n \n-\t\t\t\t\tif (!exact)\n-\t\t\t\t\t\t_clar.verbosity = MAX(_clar.verbosity, 1);\n-\n \t\t\t\t\tswitch (action) {\n \t\t\t\t\tcase 's': {\n \t\t\t\t\t\tstruct clar_explicit *explicit;\n@@ -517,23 +520,37 @@ clar_parse_args(int argc, char **argv)\n \n \t\t\tif (!found)\n \t\t\t\tclar_abort(\"No suite matching '%s' found.\\n\", argument);\n+\n \t\t\tbreak;\n \t\t}\n \n \t\tcase 'q':\n-\t\t\t_clar.report_errors_only = 1;\n+\t\t\tif (argument[2] != '\\0')\n+\t\t\t\tclar_usage(argv[0]);\n+\n+\t\t\t_clar.verbosity--;\n \t\t\tbreak;\n \n \t\tcase 'Q':\n+\t\t\tif (argument[2] != '\\0')\n+\t\t\t\tclar_usage(argv[0]);\n+\n \t\t\t_clar.exit_on_error = 1;\n \t\t\tbreak;\n \n \t\tcase 't':\n+\t\t\tif (argument[2] != '\\0')\n+\t\t\t\tclar_usage(argv[0]);\n+\n \t\t\t_clar.output_format = CL_OUTPUT_TAP;\n \t\t\tbreak;\n \n \t\tcase 'l': {\n \t\t\tsize_t j;\n+\n+\t\t\tif (argument[2] != '\\0')\n+\t\t\t\tclar_usage(argv[0]);\n+\n \t\t\tprintf(\"Test suites (use -s<name> to run just one):\\n\");\n \t\t\tfor (j = 0; j < _clar_suite_count; ++j)\n \t\t\t\tprintf(\" %3d: %s\\n\", (int)j, _clar_suites[j].name);\n@@ -542,23 +559,27 @@ clar_parse_args(int argc, char **argv)\n \t\t}\n \n \t\tcase 'v':\n+\t\t\tif (argument[2] != '\\0')\n+\t\t\t\tclar_usage(argv[0]);\n+\n \t\t\t_clar.verbosity++;\n \t\t\tbreak;\n \n \t\tcase 'r':\n \t\t\t_clar.write_summary = 1;\n \t\t\tfree(_clar.summary_filename);\n+\n \t\t\tif (*(argument + 2)) {\n \t\t\t\tif ((_clar.summary_filename = strdup(argument + 2)) == NULL)\n \t\t\t\t\tclar_abort(\"Failed to allocate summary filename.\\n\");\n \t\t\t} else {\n \t\t\t\t_clar.summary_filename = NULL;\n \t\t\t}\n+\n \t\t\tbreak;\n \n \t\tdefault:\n-\t\t\tclar_abort(\"Unexpected commandline argument '%s'.\\n\",\n-\t\t\t\t   argument[1]);\n+\t\t\tclar_usage(argv[0]);\n \t\t}\n \t}\n }\n@@ -591,7 +612,7 @@ clar_test_init(int argc, char **argv)\n \tif (_clar.write_summary)\n \t    _clar.summary = clar_summary_init(_clar.summary_filename);\n \n-\tclar_sandbox();\n+\tclar_tempdir_init();\n }\n \n int\n@@ -623,7 +644,7 @@ clar_test_shutdown(void)\n \t\t_clar.total_errors\n \t);\n \n-\tclar_unsandbox();\n+\tclar_tempdir_shutdown();\n \n \tif (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0)\n \t\tclar_abort(\"Failed to write the summary file '%s: %s.\\n\",\n@@ -635,6 +656,14 @@ clar_test_shutdown(void)\n \t}\n \n \tfor (report = _clar.reports; report; report = report_next) {\n+\t\tstruct clar_error *error, *error_next;\n+\n+\t\tfor (error = report->errors; error; error = error_next) {\n+\t\t\tfree(error->description);\n+\t\t\terror_next = error->next;\n+\t\t\tfree(error);\n+\t\t}\n+\n \t\treport_next = report->next;\n \t\tfree(report);\n \t}\n@@ -660,7 +689,7 @@ static void abort_test(void)\n \t\tclar_print_onabort(\n \t\t\t\t\"Fatal error: a cleanup method raised an exception.\\n\");\n \t\tclar_report_errors(_clar.last_report);\n-\t\texit(-1);\n+\t\texit(1);\n \t}\n \n \tCL_TRACE(CL_TRACE__TEST__LONGJMP);\n@@ -695,9 +724,9 @@ void clar__fail(\n \n \t_clar.last_report->last_error = error;\n \n-\terror->file = file;\n-\terror->function = function;\n-\terror->line_number = line;\n+\terror->file = _clar.invoke_file ? _clar.invoke_file : file;\n+\terror->function = _clar.invoke_func ? _clar.invoke_func : function;\n+\terror->line_number = _clar.invoke_line ? _clar.invoke_line : line;\n \terror->error_msg = error_msg;\n \n \tif (description != NULL &&\n@@ -754,7 +783,12 @@ void clar__assert_equal(\n \t\t\t\tp_snprintf(buf, sizeof(buf), \"'%s' != '%s' (at byte %d)\",\n \t\t\t\t\ts1, s2, pos);\n \t\t\t} else {\n-\t\t\t\tp_snprintf(buf, sizeof(buf), \"'%s' != '%s'\", s1, s2);\n+\t\t\t\tconst char *q1 = s1 ? \"'\" : \"\";\n+\t\t\t\tconst char *q2 = s2 ? \"'\" : \"\";\n+\t\t\t\ts1 = s1 ? s1 : \"NULL\";\n+\t\t\t\ts2 = s2 ? s2 : \"NULL\";\n+\t\t\t\tp_snprintf(buf, sizeof(buf), \"%s%s%s != %s%s%s\",\n+\t\t\t\t\t   q1, s1, q1, q2, s2, q2);\n \t\t\t}\n \t\t}\n \t}\n@@ -767,12 +801,17 @@ void clar__assert_equal(\n \t\tif (!is_equal) {\n \t\t\tif (s1 && s2) {\n \t\t\t\tint pos;\n-\t\t\t\tfor (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos)\n+\t\t\t\tfor (pos = 0; pos < len && s1[pos] == s2[pos]; ++pos)\n \t\t\t\t\t/* find differing byte offset */;\n \t\t\t\tp_snprintf(buf, sizeof(buf), \"'%.*s' != '%.*s' (at byte %d)\",\n \t\t\t\t\tlen, s1, len, s2, pos);\n \t\t\t} else {\n-\t\t\t\tp_snprintf(buf, sizeof(buf), \"'%.*s' != '%.*s'\", len, s1, len, s2);\n+\t\t\t\tconst char *q1 = s1 ? \"'\" : \"\";\n+\t\t\t\tconst char *q2 = s2 ? \"'\" : \"\";\n+\t\t\t\ts1 = s1 ? s1 : \"NULL\";\n+\t\t\t\ts2 = s2 ? s2 : \"NULL\";\n+\t\t\t\tp_snprintf(buf, sizeof(buf), \"%s%.*s%s != %s%.*s%s\",\n+\t\t\t\t\t   q1, len, s1, q1, q2, len, s2, q2);\n \t\t\t}\n \t\t}\n \t}\n@@ -790,7 +829,12 @@ void clar__assert_equal(\n \t\t\t\tp_snprintf(buf, sizeof(buf), \"'%ls' != '%ls' (at byte %d)\",\n \t\t\t\t\twcs1, wcs2, pos);\n \t\t\t} else {\n-\t\t\t\tp_snprintf(buf, sizeof(buf), \"'%ls' != '%ls'\", wcs1, wcs2);\n+\t\t\t\tconst char *q1 = wcs1 ? \"'\" : \"\";\n+\t\t\t\tconst char *q2 = wcs2 ? \"'\" : \"\";\n+\t\t\t\twcs1 = wcs1 ? wcs1 : L\"NULL\";\n+\t\t\t\twcs2 = wcs2 ? wcs2 : L\"NULL\";\n+\t\t\t\tp_snprintf(buf, sizeof(buf), \"%s%ls%s != %s%ls%s\",\n+\t\t\t\t\t   q1, wcs1, q1, q2, wcs2, q2);\n \t\t\t}\n \t\t}\n \t}\n@@ -803,12 +847,17 @@ void clar__assert_equal(\n \t\tif (!is_equal) {\n \t\t\tif (wcs1 && wcs2) {\n \t\t\t\tint pos;\n-\t\t\t\tfor (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos)\n+\t\t\t\tfor (pos = 0; pos < len && wcs1[pos] == wcs2[pos]; ++pos)\n \t\t\t\t\t/* find differing byte offset */;\n \t\t\t\tp_snprintf(buf, sizeof(buf), \"'%.*ls' != '%.*ls' (at byte %d)\",\n \t\t\t\t\tlen, wcs1, len, wcs2, pos);\n \t\t\t} else {\n-\t\t\t\tp_snprintf(buf, sizeof(buf), \"'%.*ls' != '%.*ls'\", len, wcs1, len, wcs2);\n+\t\t\t\tconst char *q1 = wcs1 ? \"'\" : \"\";\n+\t\t\t\tconst char *q2 = wcs2 ? \"'\" : \"\";\n+\t\t\t\twcs1 = wcs1 ? wcs1 : L\"NULL\";\n+\t\t\t\twcs2 = wcs2 ? wcs2 : L\"NULL\";\n+\t\t\t\tp_snprintf(buf, sizeof(buf), \"%s%.*ls%s != %s%.*ls%s\",\n+\t\t\t\t\t   q1, len, wcs1, q1, q2, len, wcs2, q2);\n \t\t\t}\n \t\t}\n \t}\n@@ -826,7 +875,8 @@ void clar__assert_equal(\n \t\tvoid *p1 = va_arg(args, void *), *p2 = va_arg(args, void *);\n \t\tis_equal = (p1 == p2);\n \t\tif (!is_equal)\n-\t\t\tp_snprintf(buf, sizeof(buf), \"%p != %p\", p1, p2);\n+\t\t\tp_snprintf(buf, sizeof(buf), \"0x%\"PRIxPTR\" != 0x%\"PRIxPTR,\n+\t\t\t\t   (uintptr_t)p1, (uintptr_t)p2);\n \t}\n \telse {\n \t\tint i1 = va_arg(args, int), i2 = va_arg(args, int);\n@@ -850,6 +900,23 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque)\n \t_clar.local_cleanup_payload = opaque;\n }\n \n+void clar__set_invokepoint(\n+\tconst char *file,\n+\tconst char *func,\n+\tsize_t line)\n+{\n+\t_clar.invoke_file = file;\n+\t_clar.invoke_func = func;\n+\t_clar.invoke_line = line;\n+}\n+\n+void clar__clear_invokepoint(void)\n+{\n+\t_clar.invoke_file = NULL;\n+\t_clar.invoke_func = NULL;\n+\t_clar.invoke_line = 0;\n+}\n+\n #include \"clar/sandbox.h\"\n #include \"clar/fixtures.h\"\n #include \"clar/fs.h\"\ndiff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h\nindex 8c22382bd56..ca72292ae91 100644\n--- a/t/unit-tests/clar/clar.h\n+++ b/t/unit-tests/clar/clar.h\n@@ -8,6 +8,25 @@\n #define __CLAR_TEST_H__\n \n #include <stdlib.h>\n+#include <limits.h>\n+\n+#if defined(_WIN32) && defined(CLAR_WIN32_LONGPATHS)\n+# define CLAR_MAX_PATH 4096\n+#elif defined(_WIN32)\n+# define CLAR_MAX_PATH MAX_PATH\n+#else\n+# define CLAR_MAX_PATH PATH_MAX\n+#endif\n+\n+#ifndef CLAR_SELFTEST\n+# define CLAR_CURRENT_FILE __FILE__\n+# define CLAR_CURRENT_LINE __LINE__\n+# define CLAR_CURRENT_FUNC __func__\n+#else\n+# define CLAR_CURRENT_FILE \"file\"\n+# define CLAR_CURRENT_LINE 42\n+# define CLAR_CURRENT_FUNC \"func\"\n+#endif\n \n enum cl_test_status {\n \tCL_TEST_OK,\n@@ -30,6 +49,7 @@ void clar_test_shutdown(void);\n int clar_test(int argc, char *argv[]);\n \n const char *clar_sandbox_path(void);\n+const char *clar_tempdir_path(void);\n \n void cl_set_cleanup(void (*cleanup)(void *), void *opaque);\n void cl_fs_cleanup(void);\n@@ -83,19 +103,33 @@ void cl_fixture_cleanup(const char *fixture_name);\n const char *cl_fixture_basename(const char *fixture_name);\n #endif\n \n+/**\n+ * Invoke a helper function, which itself will use `cl_assert`\n+ * constructs. This will preserve the stack information of the\n+ * current call point, so that function name and line number\n+ * information is shown from the line of the test, instead of\n+ * the helper function.\n+ */\n+#define cl_invoke(expr) \\\n+\tdo { \\\n+\t\tclar__set_invokepoint(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE); \\\n+\t\texpr; \\\n+\t\tclar__clear_invokepoint(); \\\n+\t} while(0)\n+\n /**\n  * Assertion macros with explicit error message\n  */\n-#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, \"Function call failed: \" #expr, desc, 1)\n-#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, \"Expected function call to fail: \" #expr, desc, 1)\n-#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, \"Expression is not true: \" #expr, desc, 1)\n+#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Function call failed: \" #expr, desc, 1)\n+#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Expected function call to fail: \" #expr, desc, 1)\n+#define cl_assert_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Expression is not true: \" #expr, desc, 1)\n \n /**\n  * Check macros with explicit error message\n  */\n-#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, \"Function call failed: \" #expr, desc, 0)\n-#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, \"Expected function call to fail: \" #expr, desc, 0)\n-#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, \"Expression is not true: \" #expr, desc, 0)\n+#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Function call failed: \" #expr, desc, 0)\n+#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Expected function call to fail: \" #expr, desc, 0)\n+#define cl_check_(expr, desc) clar__assert((expr) != 0, CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Expression is not true: \" #expr, desc, 0)\n \n /**\n  * Assertion macros with no error message\n@@ -114,33 +148,33 @@ const char *cl_fixture_basename(const char *fixture_name);\n /**\n  * Forced failure/warning\n  */\n-#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, \"Test failed.\", desc, 1)\n-#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, \"Warning during test execution:\", desc, 0)\n+#define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Test failed.\", desc, 1)\n+#define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, \"Warning during test execution:\", desc, 0)\n \n #define cl_skip() clar__skip()\n \n /**\n  * Typed assertion macros\n  */\n-#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #s1 \" != \" #s2, 1, \"%s\", (s1), (s2))\n-#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #s1 \" != \" #s2 \" (\" #note \")\", 1, \"%s\", (s1), (s2))\n+#define cl_assert_equal_s(s1,s2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #s1 \" != \" #s2, 1, \"%s\", (s1), (s2))\n+#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #s1 \" != \" #s2 \" (\" #note \")\", 1, \"%s\", (s1), (s2))\n \n-#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #wcs1 \" != \" #wcs2, 1, \"%ls\", (wcs1), (wcs2))\n-#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #wcs1 \" != \" #wcs2 \" (\" #note \")\", 1, \"%ls\", (wcs1), (wcs2))\n+#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #wcs1 \" != \" #wcs2, 1, \"%ls\", (wcs1), (wcs2))\n+#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #wcs1 \" != \" #wcs2 \" (\" #note \")\", 1, \"%ls\", (wcs1), (wcs2))\n \n-#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #s1 \" != \" #s2, 1, \"%.*s\", (s1), (s2), (int)(len))\n-#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #s1 \" != \" #s2 \" (\" #note \")\", 1, \"%.*s\", (s1), (s2), (int)(len))\n+#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #s1 \" != \" #s2, 1, \"%.*s\", (s1), (s2), (int)(len))\n+#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #s1 \" != \" #s2 \" (\" #note \")\", 1, \"%.*s\", (s1), (s2), (int)(len))\n \n-#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #wcs1 \" != \" #wcs2, 1, \"%.*ls\", (wcs1), (wcs2), (int)(len))\n-#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,\"String mismatch: \" #wcs1 \" != \" #wcs2 \" (\" #note \")\", 1, \"%.*ls\", (wcs1), (wcs2), (int)(len))\n+#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #wcs1 \" != \" #wcs2, 1, \"%.*ls\", (wcs1), (wcs2), (int)(len))\n+#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"String mismatch: \" #wcs1 \" != \" #wcs2 \" (\" #note \")\", 1, \"%.*ls\", (wcs1), (wcs2), (int)(len))\n \n-#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 \" != \" #i2, 1, \"%d\", (int)(i1), (int)(i2))\n-#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 \" != \" #i2 \" (\" #note \")\", 1, \"%d\", (i1), (i2))\n-#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 \" != \" #i2, 1, (fmt), (int)(i1), (int)(i2))\n+#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 \" != \" #i2, 1, \"%d\", (int)(i1), (int)(i2))\n+#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 \" != \" #i2 \" (\" #note \")\", 1, \"%d\", (i1), (i2))\n+#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 \" != \" #i2, 1, (fmt), (int)(i1), (int)(i2))\n \n-#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 \" != \" #b2, 1, \"%d\", (int)((b1) != 0),(int)((b2) != 0))\n+#define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 \" != \" #b2, 1, \"%d\", (int)((b1) != 0),(int)((b2) != 0))\n \n-#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,\"Pointer mismatch: \" #p1 \" != \" #p2, 1, \"%p\", (p1), (p2))\n+#define cl_assert_equal_p(p1,p2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,\"Pointer mismatch: \" #p1 \" != \" #p2, 1, \"%p\", (p1), (p2))\n \n void clar__skip(void);\n \n@@ -170,4 +204,11 @@ void clar__assert_equal(\n \tconst char *fmt,\n \t...);\n \n+void clar__set_invokepoint(\n+\tconst char *file,\n+\tconst char *func,\n+\tsize_t line);\n+\n+void clar__clear_invokepoint(void);\n+\n #endif\ndiff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h\nindex 6ec6423484d..9f1023df594 100644\n--- a/t/unit-tests/clar/clar/fixtures.h\n+++ b/t/unit-tests/clar/clar/fixtures.h\n@@ -2,7 +2,7 @@\n static const char *\n fixture_path(const char *base, const char *fixture_name)\n {\n-\tstatic char _path[4096];\n+\tstatic char _path[CLAR_MAX_PATH];\n \tsize_t root_len;\n \n \troot_len = strlen(base);\n@@ -28,7 +28,7 @@ const char *cl_fixture(const char *fixture_name)\n \n void cl_fixture_sandbox(const char *fixture_name)\n {\n-\tfs_copy(cl_fixture(fixture_name), _clar_path);\n+\tfs_copy(cl_fixture(fixture_name), clar_sandbox_path());\n }\n \n const char *cl_fixture_basename(const char *fixture_name)\n@@ -45,6 +45,6 @@ const char *cl_fixture_basename(const char *fixture_name)\n \n void cl_fixture_cleanup(const char *fixture_name)\n {\n-\tfs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));\n+\tfs_rm(fixture_path(clar_sandbox_path(), cl_fixture_basename(fixture_name)));\n }\n #endif\ndiff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h\nindex 2203743fb48..f1311d91e85 100644\n--- a/t/unit-tests/clar/clar/fs.h\n+++ b/t/unit-tests/clar/clar/fs.h\n@@ -8,12 +8,6 @@\n \n #ifdef _WIN32\n \n-#ifdef CLAR_WIN32_LONGPATHS\n-# define CLAR_MAX_PATH 4096\n-#else\n-# define CLAR_MAX_PATH MAX_PATH\n-#endif\n-\n #define RM_RETRY_COUNT\t5\n #define RM_RETRY_DELAY\t10\n \n@@ -296,7 +290,7 @@ void\n cl_fs_cleanup(void)\n {\n #ifdef CLAR_FIXTURE_PATH\n-\tfs_rm(fixture_path(_clar_path, \"*\"));\n+\tfs_rm(fixture_path(clar_tempdir_path(), \"*\"));\n #else\n \t((void)fs_copy); /* unused */\n #endif\n@@ -371,17 +365,19 @@ static void\n fs_copydir_helper(const char *source, const char *dest, int dest_mode)\n {\n \tDIR *source_dir;\n-\tstruct dirent *d;\n \n \tmkdir(dest, dest_mode);\n \n \tcl_assert_(source_dir = opendir(source), \"Could not open source dir\");\n-\tfor (;;) {\n+\twhile (1) {\n+\t\tstruct dirent *d;\n \t\tchar *child;\n \n \t\terrno = 0;\n-\t\tif ((d = readdir(source_dir)) == NULL)\n+\t\td = readdir(source_dir);\n+\t\tif (!d)\n \t\t\tbreak;\n+\n \t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n \t\t\tcontinue;\n \n@@ -479,15 +475,18 @@ static void\n fs_rmdir_helper(const char *path)\n {\n \tDIR *dir;\n-\tstruct dirent *d;\n \n \tcl_assert_(dir = opendir(path), \"Could not open dir\");\n-\tfor (;;) {\n+\n+\twhile (1) {\n+\t\tstruct dirent *d;\n \t\tchar *child;\n \n \t\terrno = 0;\n-\t\tif ((d = readdir(dir)) == NULL)\n+\t\td = readdir(dir);\n+\t\tif (!d)\n \t\t\tbreak;\n+\n \t\tif (!strcmp(d->d_name, \".\") || !strcmp(d->d_name, \"..\"))\n \t\t\tcontinue;\n \n@@ -524,7 +523,7 @@ fs_rm(const char *path)\n void\n cl_fs_cleanup(void)\n {\n-\tclar_unsandbox();\n-\tclar_sandbox();\n+\tclar_tempdir_shutdown();\n+\tclar_tempdir_init();\n }\n #endif\ndiff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h\nindex 69d0ee967e7..0282aaa1381 100644\n--- a/t/unit-tests/clar/clar/print.h\n+++ b/t/unit-tests/clar/clar/print.h\n@@ -3,6 +3,10 @@\n static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)\n {\n \t(void)test_count;\n+\n+\tif (_clar.verbosity < 0)\n+\t\treturn;\n+\n \tprintf(\"Loaded %d suites: %s\\n\", (int)suite_count, suite_names);\n \tprintf(\"Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\\n\");\n }\n@@ -13,10 +17,27 @@ static void clar_print_clap_shutdown(int test_count, int suite_count, int error_\n \t(void)suite_count;\n \t(void)error_count;\n \n-\tprintf(\"\\n\\n\");\n+\tif (_clar.verbosity >= 0)\n+\t\tprintf(\"\\n\\n\");\n \tclar_report_all();\n }\n \n+\n+static void clar_print_indented(const char *str, int indent)\n+{\n+\tconst char *bol, *eol;\n+\n+\tfor (bol = str; *bol; bol = eol) {\n+\t\teol = strchr(bol, '\\n');\n+\t\tif (eol)\n+\t\t\teol++;\n+\t\telse\n+\t\t\teol = bol + strlen(bol);\n+\t\tprintf(\"%*s%.*s\", indent, \"\", (int)(eol - bol), bol);\n+\t}\n+\tputc('\\n', stdout);\n+}\n+\n static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)\n {\n \tprintf(\"  %d) Failure:\\n\", num);\n@@ -27,10 +48,10 @@ static void clar_print_clap_error(int num, const struct clar_report *report, con\n \t\terror->file,\n \t\terror->line_number);\n \n-\tprintf(\"  %s\\n\", error->error_msg);\n+\tclar_print_indented(error->error_msg, 2);\n \n \tif (error->description != NULL)\n-\t\tprintf(\"  %s\\n\", error->description);\n+\t\tclar_print_indented(error->description, 2);\n \n \tprintf(\"\\n\");\n \tfflush(stdout);\n@@ -41,14 +62,17 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name\n \t(void)test_name;\n \t(void)test_number;\n \n+\tif (_clar.verbosity < 0)\n+\t\treturn;\n+\n \tif (_clar.verbosity > 1) {\n \t\tprintf(\"%s::%s: \", suite_name, test_name);\n \n \t\tswitch (status) {\n \t\tcase CL_TEST_OK: printf(\"ok\\n\"); break;\n \t\tcase CL_TEST_FAILURE: printf(\"fail\\n\"); break;\n-\t\tcase CL_TEST_SKIP: printf(\"skipped\"); break;\n-\t\tcase CL_TEST_NOTRUN: printf(\"notrun\"); break;\n+\t\tcase CL_TEST_SKIP: printf(\"skipped\\n\"); break;\n+\t\tcase CL_TEST_NOTRUN: printf(\"notrun\\n\"); break;\n \t\t}\n \t} else {\n \t\tswitch (status) {\n@@ -64,6 +88,8 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name\n \n static void clar_print_clap_onsuite(const char *suite_name, int suite_index)\n {\n+\tif (_clar.verbosity < 0)\n+\t\treturn;\n \tif (_clar.verbosity == 1)\n \t\tprintf(\"\\n%s\", suite_name);\n \n@@ -127,18 +153,20 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,\n \tcase CL_TEST_FAILURE:\n \t\tprintf(\"not ok %d - %s::%s\\n\", test_number, suite_name, test_name);\n \n-\t\tprintf(\"    ---\\n\");\n-\t\tprintf(\"    reason: |\\n\");\n-\t\tprintf(\"      %s\\n\", error->error_msg);\n+\t\tif (_clar.verbosity >= 0) {\n+\t\t\tprintf(\"    ---\\n\");\n+\t\t\tprintf(\"    reason: |\\n\");\n+\t\t\tclar_print_indented(error->error_msg, 6);\n \n-\t\tif (error->description)\n-\t\t\tprintf(\"      %s\\n\", error->description);\n+\t\t\tif (error->description)\n+\t\t\t\tclar_print_indented(error->description, 6);\n \n-\t\tprintf(\"    at:\\n\");\n-\t\tprintf(\"      file: '\"); print_escaped(error->file); printf(\"'\\n\");\n-\t\tprintf(\"      line: %\" PRIuMAX \"\\n\", error->line_number);\n-\t\tprintf(\"      function: '%s'\\n\", error->function);\n-\t\tprintf(\"    ---\\n\");\n+\t\t\tprintf(\"    at:\\n\");\n+\t\t\tprintf(\"      file: '\"); print_escaped(error->file); printf(\"'\\n\");\n+\t\t\tprintf(\"      line: %\" PRIuMAX \"\\n\", error->line_number);\n+\t\t\tprintf(\"      function: '%s'\\n\", error->function);\n+\t\t\tprintf(\"    ---\\n\");\n+\t\t}\n \n \t\tbreak;\n \tcase CL_TEST_SKIP:\n@@ -152,6 +180,8 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,\n \n static void clar_print_tap_onsuite(const char *suite_name, int suite_index)\n {\n+\tif (_clar.verbosity < 0)\n+\t\treturn;\n \tprintf(\"# start of suite %d: %s\\n\", suite_index, suite_name);\n }\n \ndiff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h\nindex bc960f50e0f..52add8aceba 100644\n--- a/t/unit-tests/clar/clar/sandbox.h\n+++ b/t/unit-tests/clar/clar/sandbox.h\n@@ -2,7 +2,17 @@\n #include <sys/syslimits.h>\n #endif\n \n-static char _clar_path[4096 + 1];\n+/*\n+ * The tempdir is the temporary directory for the entirety of the clar\n+ * process execution. The sandbox is an individual temporary directory\n+ * for the execution of an individual test. Sandboxes are deleted\n+ * entirely after test execution to avoid pollution across tests.\n+ */\n+\n+static char _clar_tempdir[CLAR_MAX_PATH];\n+static size_t _clar_tempdir_len;\n+\n+static char _clar_sandbox[CLAR_MAX_PATH];\n \n static int\n is_valid_tmp_path(const char *path)\n@@ -15,7 +25,10 @@ is_valid_tmp_path(const char *path)\n \tif (!S_ISDIR(st.st_mode))\n \t\treturn 0;\n \n-\treturn (access(path, W_OK) == 0);\n+\tif (access(path, W_OK) != 0)\n+\t\treturn 0;\n+\n+\treturn (strlen(path) < CLAR_MAX_PATH);\n }\n \n static int\n@@ -31,14 +44,11 @@ find_tmp_path(char *buffer, size_t length)\n \n \tfor (i = 0; i < var_count; ++i) {\n \t\tconst char *env = getenv(env_vars[i]);\n+\n \t\tif (!env)\n \t\t\tcontinue;\n \n \t\tif (is_valid_tmp_path(env)) {\n-#ifdef __APPLE__\n-\t\t\tif (length >= PATH_MAX && realpath(env, buffer) != NULL)\n-\t\t\t\treturn 0;\n-#endif\n \t\t\tstrncpy(buffer, env, length - 1);\n \t\t\tbuffer[length - 1] = '\\0';\n \t\t\treturn 0;\n@@ -47,21 +57,18 @@ find_tmp_path(char *buffer, size_t length)\n \n \t/* If the environment doesn't say anything, try to use /tmp */\n \tif (is_valid_tmp_path(\"/tmp\")) {\n-#ifdef __APPLE__\n-\t\tif (length >= PATH_MAX && realpath(\"/tmp\", buffer) != NULL)\n-\t\t\treturn 0;\n-#endif\n \t\tstrncpy(buffer, \"/tmp\", length - 1);\n \t\tbuffer[length - 1] = '\\0';\n \t\treturn 0;\n \t}\n \n #else\n-\tDWORD env_len = GetEnvironmentVariable(\"CLAR_TMP\", buffer, (DWORD)length);\n-\tif (env_len > 0 && env_len < (DWORD)length)\n+\tDWORD len = GetEnvironmentVariable(\"CLAR_TMP\", buffer, (DWORD)length);\n+\tif (len > 0 && len < (DWORD)length)\n \t\treturn 0;\n \n-\tif (GetTempPath((DWORD)length, buffer))\n+\tlen = GetTempPath((DWORD)length, buffer);\n+\tif (len > 0 && len < (DWORD)length)\n \t\treturn 0;\n #endif\n \n@@ -75,17 +82,53 @@ find_tmp_path(char *buffer, size_t length)\n \treturn -1;\n }\n \n-static void clar_unsandbox(void)\n+static int canonicalize_tmp_path(char *buffer)\n+{\n+#ifdef _WIN32\n+\tchar tmp[CLAR_MAX_PATH], *p;\n+\tDWORD ret;\n+\n+\tret = GetFullPathName(buffer, CLAR_MAX_PATH, tmp, NULL);\n+\n+\tif (ret == 0 || ret > CLAR_MAX_PATH)\n+\t\treturn -1;\n+\n+\tret = GetLongPathName(tmp, buffer, CLAR_MAX_PATH);\n+\n+\tif (ret == 0 || ret > CLAR_MAX_PATH)\n+\t\treturn -1;\n+\n+\t/* normalize path to POSIX forward slashes */\n+\tfor (p = buffer; *p; p++)\n+\t\tif (*p == '\\\\')\n+\t\t\t*p = '/';\n+\n+\treturn 0;\n+#elif defined(CLAR_HAS_REALPATH)\n+\tchar tmp[CLAR_MAX_PATH];\n+\n+\tif (realpath(buffer, tmp) == NULL)\n+\t\treturn -1;\n+\n+\tstrcpy(buffer, tmp);\n+\treturn 0;\n+#else\n+\t(void)buffer;\n+\treturn 0;\n+#endif\n+}\n+\n+static void clar_tempdir_shutdown(void)\n {\n-\tif (_clar_path[0] == '\\0')\n+\tif (_clar_tempdir[0] == '\\0')\n \t\treturn;\n \n \tcl_must_pass(chdir(\"..\"));\n \n-\tfs_rm(_clar_path);\n+\tfs_rm(_clar_tempdir);\n }\n \n-static int build_sandbox_path(void)\n+static int build_tempdir_path(void)\n {\n #ifdef CLAR_TMPDIR\n \tconst char path_tail[] = CLAR_TMPDIR \"_XXXXXX\";\n@@ -95,64 +138,153 @@ static int build_sandbox_path(void)\n \n \tsize_t len;\n \n-\tif (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)\n+\tif (find_tmp_path(_clar_tempdir, sizeof(_clar_tempdir)) < 0 ||\n+\t    canonicalize_tmp_path(_clar_tempdir) < 0)\n \t\treturn -1;\n \n-\tlen = strlen(_clar_path);\n+\tlen = strlen(_clar_tempdir);\n \n-#ifdef _WIN32\n-\t{ /* normalize path to POSIX forward slashes */\n-\t\tsize_t i;\n-\t\tfor (i = 0; i < len; ++i) {\n-\t\t\tif (_clar_path[i] == '\\\\')\n-\t\t\t\t_clar_path[i] = '/';\n-\t\t}\n-\t}\n-#endif\n+\tif (len + strlen(path_tail) + 2 > CLAR_MAX_PATH)\n+\t\treturn -1;\n \n-\tif (_clar_path[len - 1] != '/') {\n-\t\t_clar_path[len++] = '/';\n-\t}\n+\tif (_clar_tempdir[len - 1] != '/')\n+\t\t_clar_tempdir[len++] = '/';\n \n-\tstrncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);\n+\tstrncpy(_clar_tempdir + len, path_tail, sizeof(_clar_tempdir) - len);\n \n #if defined(__MINGW32__)\n-\tif (_mktemp(_clar_path) == NULL)\n+\tif (_mktemp(_clar_tempdir) == NULL)\n \t\treturn -1;\n \n-\tif (mkdir(_clar_path, 0700) != 0)\n+\tif (mkdir(_clar_tempdir, 0700) != 0)\n \t\treturn -1;\n #elif defined(_WIN32)\n-\tif (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)\n+\tif (_mktemp_s(_clar_tempdir, sizeof(_clar_tempdir)) != 0)\n \t\treturn -1;\n \n-\tif (mkdir(_clar_path, 0700) != 0)\n+\tif (mkdir(_clar_tempdir, 0700) != 0)\n \t\treturn -1;\n-#elif defined(__sun) || defined(__TANDEM)\n-\tif (mktemp(_clar_path) == NULL)\n+#elif defined(__sun) || defined(__TANDEM) || defined(__hpux)\n+\tif (mktemp(_clar_tempdir) == NULL)\n \t\treturn -1;\n \n-\tif (mkdir(_clar_path, 0700) != 0)\n+\tif (mkdir(_clar_tempdir, 0700) != 0)\n \t\treturn -1;\n #else\n-\tif (mkdtemp(_clar_path) == NULL)\n+\tif (mkdtemp(_clar_tempdir) == NULL)\n \t\treturn -1;\n #endif\n \n+\t_clar_tempdir_len = strlen(_clar_tempdir);\n \treturn 0;\n }\n \n-static void clar_sandbox(void)\n+static void clar_tempdir_init(void)\n {\n-\tif (_clar_path[0] == '\\0' && build_sandbox_path() < 0)\n-\t\tclar_abort(\"Failed to build sandbox path.\\n\");\n+\tif (_clar_tempdir[0] == '\\0' && build_tempdir_path() < 0)\n+\t\tclar_abort(\"Failed to build tempdir path.\\n\");\n \n-\tif (chdir(_clar_path) != 0)\n-\t\tclar_abort(\"Failed to change into sandbox directory '%s': %s.\\n\",\n-\t\t\t   _clar_path, strerror(errno));\n+\tif (chdir(_clar_tempdir) != 0)\n+\t\tclar_abort(\"Failed to change into tempdir '%s': %s.\\n\",\n+\t\t\t   _clar_tempdir, strerror(errno));\n+\n+#if !defined(CLAR_SANDBOX_TEST_NAMES) && defined(_WIN32)\n+\tsrand(clock() ^ (unsigned int)time(NULL) ^ GetCurrentProcessId() ^ GetCurrentThreadId());\n+#elif !defined(CLAR_SANDBOX_TEST_NAMES)\n+\tsrand(clock() ^ time(NULL) ^ ((unsigned)getpid() << 16));\n+#endif\n+}\n+\n+static void append(char *dst, const char *src)\n+{\n+\tchar *d;\n+\tconst char *s;\n+\n+\tfor (d = dst; *d; d++)\n+\t\t;\n+\n+\tfor (s = src; *s; d++, s++)\n+\t\tif (*s == ':')\n+\t\t\t*d = '_';\n+\t\telse\n+\t\t\t*d = *s;\n+\n+\t*d = '\\0';\n+}\n+\n+static int clar_sandbox_create(const char *suite_name, const char *test_name)\n+{\n+#ifndef CLAR_SANDBOX_TEST_NAMES\n+\tchar alpha[] = \"0123456789abcdef\";\n+\tint num = rand();\n+#endif\n+\n+\tcl_assert(_clar_sandbox[0] == '\\0');\n+\n+\t/*\n+\t * We may want to use test names as sandbox directory names for\n+\t * readability, _however_ on platforms with restrictions for short\n+\t * file / folder names (eg, Windows), this may be too long.\n+\t */\n+#ifdef CLAR_SANDBOX_TEST_NAMES\n+\tcl_assert(strlen(_clar_tempdir) + strlen(suite_name) + strlen(test_name) + 3 < CLAR_MAX_PATH);\n+\n+\tstrcpy(_clar_sandbox, _clar_tempdir);\n+\t_clar_sandbox[_clar_tempdir_len] = '/';\n+\t_clar_sandbox[_clar_tempdir_len + 1] = '\\0';\n+\n+\tappend(_clar_sandbox, suite_name);\n+\tappend(_clar_sandbox, \"__\");\n+\tappend(_clar_sandbox, test_name);\n+#else\n+\t((void)suite_name);\n+\t((void)test_name);\n+\t((void)append);\n+\n+\tcl_assert(strlen(_clar_tempdir) + 9 < CLAR_MAX_PATH);\n+\n+\tstrcpy(_clar_sandbox, _clar_tempdir);\n+\t_clar_sandbox[_clar_tempdir_len] = '/';\n+\n+\t_clar_sandbox[_clar_tempdir_len + 1] = alpha[(num & 0xf0000000) >> 28];\n+\t_clar_sandbox[_clar_tempdir_len + 2] = alpha[(num & 0x0f000000) >> 24];\n+\t_clar_sandbox[_clar_tempdir_len + 3] = alpha[(num & 0x00f00000) >> 20];\n+\t_clar_sandbox[_clar_tempdir_len + 4] = alpha[(num & 0x000f0000) >> 16];\n+\t_clar_sandbox[_clar_tempdir_len + 5] = alpha[(num & 0x0000f000) >> 12];\n+\t_clar_sandbox[_clar_tempdir_len + 6] = alpha[(num & 0x00000f00) >> 8];\n+\t_clar_sandbox[_clar_tempdir_len + 7] = alpha[(num & 0x000000f0) >> 4];\n+\t_clar_sandbox[_clar_tempdir_len + 8] = alpha[(num & 0x0000000f) >> 0];\n+\t_clar_sandbox[_clar_tempdir_len + 9] = '\\0';\n+#endif\n+\n+\tif (mkdir(_clar_sandbox, 0700) != 0)\n+\t\treturn -1;\n+\n+\tif (chdir(_clar_sandbox) != 0)\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int clar_sandbox_cleanup(void)\n+{\n+\tcl_assert(_clar_sandbox[0] != '\\0');\n+\n+\tif (chdir(_clar_tempdir) != 0)\n+\t\treturn -1;\n+\n+\tfs_rm(_clar_sandbox);\n+\t_clar_sandbox[0] = '\\0';\n+\n+\treturn 0;\n+}\n+\n+const char *clar_tempdir_path(void)\n+{\n+\treturn _clar_tempdir;\n }\n \n const char *clar_sandbox_path(void)\n {\n-\treturn _clar_path;\n+\treturn _clar_sandbox;\n }\ndiff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h\nindex 0d0b646fe75..7b85f162d8e 100644\n--- a/t/unit-tests/clar/clar/summary.h\n+++ b/t/unit-tests/clar/clar/summary.h\n@@ -23,10 +23,11 @@ static int clar_summary_testsuite(struct clar_summary *summary,\n     int idn, const char *name, time_t timestamp,\n     int test_count, int fail_count, int error_count)\n {\n-\tstruct tm *tm = localtime(&timestamp);\n+\tstruct tm tm;\n \tchar iso_dt[20];\n \n-\tif (strftime(iso_dt, sizeof(iso_dt), \"%Y-%m-%dT%H:%M:%S\", tm) == 0)\n+\tlocaltime_r(&timestamp, &tm);\n+\tif (strftime(iso_dt, sizeof(iso_dt), \"%Y-%m-%dT%H:%M:%S\", &tm) == 0)\n \t\treturn -1;\n \n \treturn fprintf(summary->fp, \"\\t<testsuite\"\ndiff --git a/t/unit-tests/clar/example/CMakeLists.txt b/t/unit-tests/clar/example/CMakeLists.txt\nnew file mode 100644\nindex 00000000000..b72f1875236\n--- /dev/null\n+++ b/t/unit-tests/clar/example/CMakeLists.txt\n@@ -0,0 +1,28 @@\n+find_package(Python COMPONENTS Interpreter REQUIRED)\n+\n+add_custom_command(OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/clar.suite\"\n+\tCOMMAND \"${Python_EXECUTABLE}\" \"${CMAKE_SOURCE_DIR}/generate.py\" --output \"${CMAKE_CURRENT_BINARY_DIR}\"\n+\tDEPENDS main.c example.c\n+\tWORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"\n+)\n+\n+add_executable(example)\n+set_target_properties(example PROPERTIES\n+\tC_STANDARD 90\n+\tC_STANDARD_REQUIRED ON\n+\tC_EXTENSIONS OFF\n+)\n+target_sources(example PRIVATE\n+\tmain.c\n+\texample.c\n+\t\"${CMAKE_CURRENT_BINARY_DIR}/clar.suite\"\n+)\n+target_compile_definitions(example PRIVATE)\n+target_compile_options(example PRIVATE\n+\t$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>\n+)\n+target_include_directories(example PRIVATE\n+\t\"${CMAKE_SOURCE_DIR}\"\n+\t\"${CMAKE_CURRENT_BINARY_DIR}\"\n+)\n+target_link_libraries(example clar)\ndiff --git a/t/unit-tests/clar/example/example.c b/t/unit-tests/clar/example/example.c\nnew file mode 100644\nindex 00000000000..c07d6bf68e8\n--- /dev/null\n+++ b/t/unit-tests/clar/example/example.c\n@@ -0,0 +1,6 @@\n+#include \"clar.h\"\n+\n+void test_example__simple_assert(void)\n+{\n+\tcl_assert_equal_i(1, 1);\n+}\ndiff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/example/main.c\nsimilarity index 96%\nrename from t/unit-tests/clar/test/main.c.sample\nrename to t/unit-tests/clar/example/main.c\nindex a4d91b72fa8..f8def7fa6ed 100644\n--- a/t/unit-tests/clar/test/main.c.sample\n+++ b/t/unit-tests/clar/example/main.c\n@@ -5,7 +5,7 @@\n  * For full terms see the included COPYING file.\n  */\n \n-#include \"clar_test.h\"\n+#include \"clar.h\"\n \n /*\n  * Minimal main() for clar tests.\ndiff --git a/t/unit-tests/clar/test/CMakeLists.txt b/t/unit-tests/clar/test/CMakeLists.txt\nindex 7f2c1dc17a9..96abd6ed931 100644\n--- a/t/unit-tests/clar/test/CMakeLists.txt\n+++ b/t/unit-tests/clar/test/CMakeLists.txt\n@@ -1,13 +1,15 @@\n+add_subdirectory(selftest_suite)\n+\n find_package(Python COMPONENTS Interpreter REQUIRED)\n \n add_custom_command(OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/clar.suite\"\n \tCOMMAND \"${Python_EXECUTABLE}\" \"${CMAKE_SOURCE_DIR}/generate.py\" --output \"${CMAKE_CURRENT_BINARY_DIR}\"\n-\tDEPENDS main.c sample.c clar_test.h\n+\tDEPENDS main.c selftest.c\n \tWORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"\n )\n \n-add_executable(clar_test)\n-set_target_properties(clar_test PROPERTIES\n+add_executable(selftest)\n+set_target_properties(selftest PROPERTIES\n \tC_STANDARD 90\n \tC_STANDARD_REQUIRED ON\n \tC_EXTENSIONS OFF\n@@ -15,25 +17,38 @@ set_target_properties(clar_test PROPERTIES\n \n # MSVC generates all kinds of warnings. We may want to fix these in the future\n # and then unconditionally treat warnings as errors.\n-if(NOT MSVC)\n-\tset_target_properties(clar_test PROPERTIES\n+if (NOT MSVC)\n+\tset_target_properties(selftest PROPERTIES\n \t\tCOMPILE_WARNING_AS_ERROR ON\n \t)\n endif()\n \n-target_sources(clar_test PRIVATE\n+target_sources(selftest PRIVATE\n \tmain.c\n-\tsample.c\n+\tselftest.c\n \t\"${CMAKE_CURRENT_BINARY_DIR}/clar.suite\"\n )\n-target_compile_definitions(clar_test PRIVATE\n-\tCLAR_FIXTURE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/resources/\"\n+target_compile_definitions(selftest PRIVATE\n+\tCLAR_FIXTURE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/expected/\"\n )\n-target_compile_options(clar_test PRIVATE\n+target_compile_options(selftest PRIVATE\n \t$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>\n )\n-target_include_directories(clar_test PRIVATE\n+target_include_directories(selftest PRIVATE\n \t\"${CMAKE_SOURCE_DIR}\"\n \t\"${CMAKE_CURRENT_BINARY_DIR}\"\n )\n-target_link_libraries(clar_test clar)\n+target_link_libraries(selftest clar)\n+\n+add_test(NAME build_selftest_suite\n+\tCOMMAND \"${CMAKE_COMMAND}\" --build \"${CMAKE_BINARY_DIR}\" --config \"$<CONFIG>\" --target selftest_suite\n+)\n+set_tests_properties(build_selftest_suite PROPERTIES FIXTURES_SETUP clar_test_fixture)\n+\n+add_test(NAME build_selftest\n+\tCOMMAND \"${CMAKE_COMMAND}\" --build \"${CMAKE_BINARY_DIR}\" --config \"$<CONFIG>\" --target selftest\n+)\n+set_tests_properties(build_selftest PROPERTIES FIXTURES_SETUP clar_test_fixture)\n+\n+add_test(NAME selftest COMMAND \"${CMAKE_CURRENT_BINARY_DIR}/selftest\" \"$<TARGET_FILE:selftest_suite>\")\n+set_tests_properties(selftest PROPERTIES FIXTURES_REQUIRED clar_test_fixture)\ndiff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h\ndeleted file mode 100644\nindex 0fcaa639aa8..00000000000\n--- a/t/unit-tests/clar/test/clar_test.h\n+++ /dev/null\n@@ -1,16 +0,0 @@\n-/*\n- * Copyright (c) Vicent Marti. All rights reserved.\n- *\n- * This file is part of clar, distributed under the ISC license.\n- * For full terms see the included COPYING file.\n- */\n-#ifndef __CLAR_TEST__\n-#define __CLAR_TEST__\n-\n-/* Import the standard clar helper functions */\n-#include \"clar.h\"\n-\n-/* Your custom shared includes / defines here */\n-extern int global_test_counter;\n-\n-#endif\ndiff --git a/t/unit-tests/clar/test/expected/help b/t/unit-tests/clar/test/expected/help\nnew file mode 100644\nindex 00000000000..4b2be69f973\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/help\n@@ -0,0 +1,12 @@\n+Usage: selftest [options]\n+\n+Options:\n+  -sname        Run only the suite with `name` (can go to individual test name)\n+  -iname        Include the suite with `name`\n+  -xname        Exclude the suite with `name`\n+  -v            Increase verbosity (show suite names)\n+  -q            Decrease verbosity, inverse to -v\n+  -Q            Quit as soon as a test fails\n+  -t            Display results in tap format\n+  -l            Print suite names\n+  -r[filename]  Write summary file (to the optional filename)\ndiff --git a/t/unit-tests/clar/test/expected/quiet b/t/unit-tests/clar/test/expected/quiet\nnew file mode 100644\nindex 00000000000..975164147ff\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/quiet\n@@ -0,0 +1,49 @@\n+  1) Failure:\n+selftest::suite::1 [file:42]\n+  Function call failed: -1\n+\n+  2) Failure:\n+selftest::suite::2 [file:42]\n+  Expression is not true: 100 == 101\n+\n+  3) Failure:\n+selftest::suite::strings [file:42]\n+  String mismatch: \"mismatched\" != actual (\"this one fails\")\n+  'mismatched' != 'expected' (at byte 0)\n+\n+  4) Failure:\n+selftest::suite::strings_with_length [file:42]\n+  String mismatch: \"exactly\" != actual (\"this one fails\")\n+  'exa' != 'exp' (at byte 2)\n+\n+  5) Failure:\n+selftest::suite::int [file:42]\n+  101 != value (\"extra note on failing test\")\n+  101 != 100\n+\n+  6) Failure:\n+selftest::suite::int_fmt [file:42]\n+  022 != value\n+  0022 != 0144\n+\n+  7) Failure:\n+selftest::suite::bool [file:42]\n+  0 != value\n+  0 != 1\n+\n+  8) Failure:\n+selftest::suite::ptr [file:42]\n+  Pointer mismatch: p1 != p2\n+  0x1 != 0x2\n+\n+  9) Failure:\n+selftest::suite::multiline_description [file:42]\n+  Function call failed: -1\n+  description line 1\n+  description line 2\n+\n+  10) Failure:\n+selftest::suite::null_string [file:42]\n+  String mismatch: \"expected\" != actual (\"this one fails\")\n+  'expected' != NULL\n+\ndiff --git a/t/unit-tests/clar/test/expected/specific_test b/t/unit-tests/clar/test/expected/specific_test\nnew file mode 100644\nindex 00000000000..afa21509804\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/specific_test\n@@ -0,0 +1,9 @@\n+Loaded 1 suites: \n+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n+F\n+\n+  1) Failure:\n+selftest::suite::bool [file:42]\n+  0 != value\n+  0 != 1\n+\ndiff --git a/t/unit-tests/clar/test/expected/stop_on_failure b/t/unit-tests/clar/test/expected/stop_on_failure\nnew file mode 100644\nindex 00000000000..1156ade0f92\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/stop_on_failure\n@@ -0,0 +1,8 @@\n+Loaded 1 suites: \n+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n+F\n+\n+  1) Failure:\n+selftest::suite::1 [file:42]\n+  Function call failed: -1\n+\ndiff --git a/t/unit-tests/clar/test/expected/suite_names b/t/unit-tests/clar/test/expected/suite_names\nnew file mode 100644\nindex 00000000000..1b0f6397eb3\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/suite_names\n@@ -0,0 +1,2 @@\n+Test suites (use -s<name> to run just one):\n+   0: selftest::suite\ndiff --git a/t/unit-tests/clar/test/expected/summary.xml b/t/unit-tests/clar/test/expected/summary.xml\nnew file mode 100644\nindex 00000000000..9034a03d1fa\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/summary.xml\n@@ -0,0 +1,45 @@\n+<testsuites>\n+\t<testsuite id=\"0\" name=\"selftest\" hostname=\"localhost\" timestamp=\"2024-09-06T10:04:08\" tests=\"8\" failures=\"8\" errors=\"0\">\n+\t\t<testcase name=\"1\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[Function call failed: -1\n+(null)]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"2\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[Expression is not true: 100 == 101\n+(null)]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"strings\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[String mismatch: \"mismatched\" != actual (\"this one fails\")\n+'mismatched' != 'expected' (at byte 0)]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"strings_with_length\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[String mismatch: \"exactly\" != actual (\"this one fails\")\n+'exa' != 'exp' (at byte 2)]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"int\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[101 != value (\"extra note on failing test\")\n+101 != 100]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"int_fmt\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[022 != value\n+0022 != 0144]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"bool\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[0 != value\n+0 != 1]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"ptr\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[Pointer mismatch: p1 != p2\n+0x1 != 0x2]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"multiline_description\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[Function call failed: −1\n+description line 1\n+description line 2]]></failure>\n+\t\t</testcase>\n+\t\t<testcase name=\"null_string\" classname=\"selftest\" time=\"0.00\">\n+\t\t\t<failure type=\"assert\"><![CDATA[String mismatch: \"expected\" != actual (\"this one fails\")\n+'expected' != NULL]]></failure>\n+\t\t</testcase>\n+\t</testsuite>\n+</testsuites>\ndiff --git a/t/unit-tests/clar/test/expected/summary_with_filename b/t/unit-tests/clar/test/expected/summary_with_filename\nnew file mode 100644\nindex 00000000000..a5f4d405370\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/summary_with_filename\n@@ -0,0 +1,54 @@\n+Loaded 1 suites: \n+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n+FFFFFFFFFF\n+\n+  1) Failure:\n+selftest::suite::1 [file:42]\n+  Function call failed: -1\n+\n+  2) Failure:\n+selftest::suite::2 [file:42]\n+  Expression is not true: 100 == 101\n+\n+  3) Failure:\n+selftest::suite::strings [file:42]\n+  String mismatch: \"mismatched\" != actual (\"this one fails\")\n+  'mismatched' != 'expected' (at byte 0)\n+\n+  4) Failure:\n+selftest::suite::strings_with_length [file:42]\n+  String mismatch: \"exactly\" != actual (\"this one fails\")\n+  'exa' != 'exp' (at byte 2)\n+\n+  5) Failure:\n+selftest::suite::int [file:42]\n+  101 != value (\"extra note on failing test\")\n+  101 != 100\n+\n+  6) Failure:\n+selftest::suite::int_fmt [file:42]\n+  022 != value\n+  0022 != 0144\n+\n+  7) Failure:\n+selftest::suite::bool [file:42]\n+  0 != value\n+  0 != 1\n+\n+  8) Failure:\n+selftest::suite::ptr [file:42]\n+  Pointer mismatch: p1 != p2\n+  0x1 != 0x2\n+\n+  9) Failure:\n+selftest::suite::multiline_description [file:42]\n+  Function call failed: -1\n+  description line 1\n+  description line 2\n+\n+  10) Failure:\n+selftest::suite::null_string [file:42]\n+  String mismatch: \"expected\" != actual (\"this one fails\")\n+  'expected' != NULL\n+\n+written summary file to different.xml\ndiff --git a/t/unit-tests/clar/test/expected/summary_without_filename b/t/unit-tests/clar/test/expected/summary_without_filename\nnew file mode 100644\nindex 00000000000..5984502773e\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/summary_without_filename\n@@ -0,0 +1,54 @@\n+Loaded 1 suites: \n+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n+FFFFFFFFFF\n+\n+  1) Failure:\n+selftest::suite::1 [file:42]\n+  Function call failed: -1\n+\n+  2) Failure:\n+selftest::suite::2 [file:42]\n+  Expression is not true: 100 == 101\n+\n+  3) Failure:\n+selftest::suite::strings [file:42]\n+  String mismatch: \"mismatched\" != actual (\"this one fails\")\n+  'mismatched' != 'expected' (at byte 0)\n+\n+  4) Failure:\n+selftest::suite::strings_with_length [file:42]\n+  String mismatch: \"exactly\" != actual (\"this one fails\")\n+  'exa' != 'exp' (at byte 2)\n+\n+  5) Failure:\n+selftest::suite::int [file:42]\n+  101 != value (\"extra note on failing test\")\n+  101 != 100\n+\n+  6) Failure:\n+selftest::suite::int_fmt [file:42]\n+  022 != value\n+  0022 != 0144\n+\n+  7) Failure:\n+selftest::suite::bool [file:42]\n+  0 != value\n+  0 != 1\n+\n+  8) Failure:\n+selftest::suite::ptr [file:42]\n+  Pointer mismatch: p1 != p2\n+  0x1 != 0x2\n+\n+  9) Failure:\n+selftest::suite::multiline_description [file:42]\n+  Function call failed: -1\n+  description line 1\n+  description line 2\n+\n+  10) Failure:\n+selftest::suite::null_string [file:42]\n+  String mismatch: \"expected\" != actual (\"this one fails\")\n+  'expected' != NULL\n+\n+written summary file to summary.xml\ndiff --git a/t/unit-tests/clar/test/expected/tap b/t/unit-tests/clar/test/expected/tap\nnew file mode 100644\nindex 00000000000..3dc4973dfa1\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/tap\n@@ -0,0 +1,102 @@\n+TAP version 13\n+# start of suite 1: selftest::suite\n+not ok 1 - selftest::suite::1\n+    ---\n+    reason: |\n+      Function call failed: -1\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 2 - selftest::suite::2\n+    ---\n+    reason: |\n+      Expression is not true: 100 == 101\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 3 - selftest::suite::strings\n+    ---\n+    reason: |\n+      String mismatch: \"mismatched\" != actual (\"this one fails\")\n+      'mismatched' != 'expected' (at byte 0)\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 4 - selftest::suite::strings_with_length\n+    ---\n+    reason: |\n+      String mismatch: \"exactly\" != actual (\"this one fails\")\n+      'exa' != 'exp' (at byte 2)\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 5 - selftest::suite::int\n+    ---\n+    reason: |\n+      101 != value (\"extra note on failing test\")\n+      101 != 100\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 6 - selftest::suite::int_fmt\n+    ---\n+    reason: |\n+      022 != value\n+      0022 != 0144\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 7 - selftest::suite::bool\n+    ---\n+    reason: |\n+      0 != value\n+      0 != 1\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 8 - selftest::suite::ptr\n+    ---\n+    reason: |\n+      Pointer mismatch: p1 != p2\n+      0x1 != 0x2\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 9 - selftest::suite::multiline_description\n+    ---\n+    reason: |\n+      Function call failed: -1\n+      description line 1\n+      description line 2\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+not ok 10 - selftest::suite::null_string\n+    ---\n+    reason: |\n+      String mismatch: \"expected\" != actual (\"this one fails\")\n+      'expected' != NULL\n+    at:\n+      file: 'file'\n+      line: 42\n+      function: 'func'\n+    ---\n+1..10\ndiff --git a/t/unit-tests/clar/test/expected/without_arguments b/t/unit-tests/clar/test/expected/without_arguments\nnew file mode 100644\nindex 00000000000..08b67b874cb\n--- /dev/null\n+++ b/t/unit-tests/clar/test/expected/without_arguments\n@@ -0,0 +1,53 @@\n+Loaded 1 suites: \n+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n+FFFFFFFFFF\n+\n+  1) Failure:\n+selftest::suite::1 [file:42]\n+  Function call failed: -1\n+\n+  2) Failure:\n+selftest::suite::2 [file:42]\n+  Expression is not true: 100 == 101\n+\n+  3) Failure:\n+selftest::suite::strings [file:42]\n+  String mismatch: \"mismatched\" != actual (\"this one fails\")\n+  'mismatched' != 'expected' (at byte 0)\n+\n+  4) Failure:\n+selftest::suite::strings_with_length [file:42]\n+  String mismatch: \"exactly\" != actual (\"this one fails\")\n+  'exa' != 'exp' (at byte 2)\n+\n+  5) Failure:\n+selftest::suite::int [file:42]\n+  101 != value (\"extra note on failing test\")\n+  101 != 100\n+\n+  6) Failure:\n+selftest::suite::int_fmt [file:42]\n+  022 != value\n+  0022 != 0144\n+\n+  7) Failure:\n+selftest::suite::bool [file:42]\n+  0 != value\n+  0 != 1\n+\n+  8) Failure:\n+selftest::suite::ptr [file:42]\n+  Pointer mismatch: p1 != p2\n+  0x1 != 0x2\n+\n+  9) Failure:\n+selftest::suite::multiline_description [file:42]\n+  Function call failed: -1\n+  description line 1\n+  description line 2\n+\n+  10) Failure:\n+selftest::suite::null_string [file:42]\n+  String mismatch: \"expected\" != actual (\"this one fails\")\n+  'expected' != NULL\n+\ndiff --git a/t/unit-tests/clar/test/main.c b/t/unit-tests/clar/test/main.c\nindex 59e56ad255b..b1ba2996f13 100644\n--- a/t/unit-tests/clar/test/main.c\n+++ b/t/unit-tests/clar/test/main.c\n@@ -1,23 +1,9 @@\n-/*\n- * Copyright (c) Vicent Marti. All rights reserved.\n- *\n- * This file is part of clar, distributed under the ISC license.\n- * For full terms see the included COPYING file.\n- */\n+#include <stdio.h>\n+#include <string.h>\n \n-#include \"clar_test.h\"\n+#include \"selftest.h\"\n \n-/*\n- * Sample main() for clar tests.\n- *\n- * You should write your own main routine for clar tests that does specific\n- * setup and teardown as necessary for your application.  The only required\n- * line is the call to `clar_test(argc, argv)`, which will execute the test\n- * suite.  If you want to check the return value of the test application,\n- * your main() should return the same value returned by clar_test().\n- */\n-\n-int global_test_counter = 0;\n+const char *selftest_binary_path;\n \n #ifdef _WIN32\n int __cdecl main(int argc, char *argv[])\n@@ -25,16 +11,15 @@ int __cdecl main(int argc, char *argv[])\n int main(int argc, char *argv[])\n #endif\n {\n-\tint ret;\n-\n-\t/* Your custom initialization here */\n-\tglobal_test_counter = 0;\n-\n-\t/* Run the test suite */\n-\tret = clar_test(argc, argv);\n+\tif (argc < 2) {\n+\t\tfprintf(stderr, \"usage: %s <selftest-suite-executable> <options>\\n\",\n+\t\t\targv[0]);\n+\t\texit(1);\n+\t}\n \n-\t/* Your custom cleanup here */\n-\tcl_assert_equal_i(8, global_test_counter);\n+\tselftest_binary_path = argv[1];\n+\tmemmove(argv + 1, argv + 2, argc - 1);\n+\targc -= 1;\n \n-\treturn ret;\n+\treturn clar_test(argc, argv);\n }\ndiff --git a/t/unit-tests/clar/test/selftest.c b/t/unit-tests/clar/test/selftest.c\nnew file mode 100644\nindex 00000000000..abd585f4e4a\n--- /dev/null\n+++ b/t/unit-tests/clar/test/selftest.c\n@@ -0,0 +1,289 @@\n+#include <stdarg.h>\n+#include <stdio.h>\n+#include <string.h>\n+#include <sys/stat.h>\n+\n+#include \"selftest.h\"\n+\n+#ifdef _WIN32\n+# define WIN32_LEAN_AND_MEAN\n+# include <windows.h>\n+\n+static char *read_full(HANDLE h, int is_pipe)\n+{\n+\tchar *data = NULL;\n+\tsize_t data_size = 0;\n+\n+\twhile (1) {\n+\t\tCHAR buf[4096];\n+\t\tDWORD bytes_read;\n+\n+\t\tif (!ReadFile(h, buf, sizeof(buf), &bytes_read, NULL)) {\n+\t\t\tif (!is_pipe)\n+\t\t\t\tcl_fail(\"Failed reading file handle.\");\n+\t\t\tcl_assert_equal_i(GetLastError(), ERROR_BROKEN_PIPE);\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (!bytes_read)\n+\t\t\tbreak;\n+\n+\t\tdata = realloc(data, data_size + bytes_read);\n+\t\tcl_assert(data);\n+\t\tmemcpy(data + data_size, buf, bytes_read);\n+\t\tdata_size += bytes_read;\n+\t}\n+\n+\tdata = realloc(data, data_size + 1);\n+\tcl_assert(data);\n+\tdata[data_size] = '\\0';\n+\n+\twhile (strstr(data, \"\\r\\n\")) {\n+\t\tchar *ptr = strstr(data, \"\\r\\n\");\n+\t\tmemmove(ptr, ptr + 1, strlen(ptr));\n+\t}\n+\n+\treturn data;\n+}\n+\n+static char *read_file(const char *path)\n+{\n+\tchar *content;\n+\tHANDLE file;\n+\n+\tfile = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL,\n+\t\t\t  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n+\tcl_assert(file != INVALID_HANDLE_VALUE);\n+\tcontent = read_full(file, 0);\n+\tcl_assert_equal_b(1, CloseHandle(file));\n+\n+\treturn content;\n+}\n+\n+static void run(const char *expected_output_file, int expected_error_code, ...)\n+{\n+\tSECURITY_ATTRIBUTES security_attributes = { 0 };\n+\tPROCESS_INFORMATION process_info = { 0 };\n+\tSTARTUPINFO startup_info = { 0 };\n+\tchar cmdline[4096] = { 0 };\n+\tchar *expected_output = NULL;\n+\tchar *output = NULL;\n+\tHANDLE stdout_write;\n+\tHANDLE stdout_read;\n+\tDWORD exit_code;\n+\tva_list ap;\n+\n+\t/*\n+\t * Assemble command line arguments. In theory we'd have to properly\n+\t * quote them. In practice none of our tests actually care.\n+\t */\n+\tva_start(ap, expected_error_code);\n+\tsnprintf(cmdline, sizeof(cmdline), \"selftest\");\n+\twhile (1) {\n+\t\tsize_t cmdline_len = strlen(cmdline);\n+\t\tconst char *arg;\n+\n+\t\targ = va_arg(ap, const char *);\n+\t\tif (!arg)\n+\t\t\tbreak;\n+\n+\t\tcl_assert(cmdline_len + strlen(arg) < sizeof(cmdline));\n+\t\tsnprintf(cmdline + cmdline_len, sizeof(cmdline) - cmdline_len,\n+\t\t\t \" %s\", arg);\n+\t}\n+\tva_end(ap);\n+\n+\t/*\n+\t * Create a pipe that we will use to read data from the child process.\n+\t * The writing side needs to be inheritable such that the child can use\n+\t * it as stdout and stderr. The reading side should only be used by the\n+\t * parent.\n+\t */\n+\tsecurity_attributes.nLength = sizeof(security_attributes);\n+\tsecurity_attributes.bInheritHandle = TRUE;\n+\tcl_assert_equal_b(1, CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0));\n+\tcl_assert_equal_b(1, SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0));\n+\n+\t/*\n+\t * Create the child process with our pipe.\n+\t */\n+\tstartup_info.cb = sizeof(startup_info);\n+\tstartup_info.hStdError = stdout_write;\n+\tstartup_info.hStdOutput = stdout_write;\n+\tstartup_info.dwFlags |= STARTF_USESTDHANDLES;\n+\tcl_assert_equal_b(1, CreateProcess(selftest_binary_path, cmdline, NULL, NULL, TRUE,\n+\t\t\t\t\t   0, NULL, NULL, &startup_info, &process_info));\n+\tcl_assert_equal_b(1, CloseHandle(stdout_write));\n+\n+\toutput = read_full(stdout_read, 1);\n+\tcl_assert_equal_b(1, CloseHandle(stdout_read));\n+\tcl_assert_equal_b(1, GetExitCodeProcess(process_info.hProcess, &exit_code));\n+\n+\texpected_output = read_file(cl_fixture(expected_output_file));\n+\tcl_assert_equal_s(output, expected_output);\n+\tcl_assert_equal_i(exit_code, expected_error_code);\n+\n+\tfree(expected_output);\n+\tfree(output);\n+}\n+\n+#else\n+# include <errno.h>\n+# include <fcntl.h>\n+# include <limits.h>\n+# include <unistd.h>\n+# include <sys/wait.h>\n+\n+static char *read_full(int fd)\n+{\n+\tsize_t data_bytes = 0;\n+\tchar *data = NULL;\n+\n+\twhile (1) {\n+\t\tchar buf[4096];\n+\t\tssize_t n;\n+\n+\t\tn = read(fd, buf, sizeof(buf));\n+\t\tif (n < 0) {\n+\t\t\tif (errno == EAGAIN || errno == EINTR)\n+\t\t\t\tcontinue;\n+\t\t\tcl_fail(\"Failed reading from child process.\");\n+\t\t}\n+\t\tif (!n)\n+\t\t\tbreak;\n+\n+\t\tdata = realloc(data, data_bytes + n);\n+\t\tcl_assert(data);\n+\n+\t\tmemcpy(data + data_bytes, buf, n);\n+\t\tdata_bytes += n;\n+\t}\n+\n+\tdata = realloc(data, data_bytes + 1);\n+\tcl_assert(data);\n+\tdata[data_bytes] = '\\0';\n+\n+\treturn data;\n+}\n+\n+static char *read_file(const char *path)\n+{\n+\tchar *data;\n+\tint fd;\n+\n+\tfd = open(path, O_RDONLY);\n+\tif (fd < 0)\n+\t\tcl_fail(\"Failed reading expected file.\");\n+\n+\tdata = read_full(fd);\n+\tcl_must_pass(close(fd));\n+\n+\treturn data;\n+}\n+\n+static void run(const char *expected_output_file, int expected_error_code, ...)\n+{\n+\tconst char *argv[16];\n+\tint pipe_fds[2];\n+\tva_list ap;\n+\tpid_t pid;\n+\tint i;\n+\n+\tva_start(ap, expected_error_code);\n+\targv[0] = \"selftest\";\n+\tfor (i = 1; ; i++) {\n+\t\tcl_assert(i < sizeof(argv) / sizeof(*argv));\n+\n+\t\targv[i] = va_arg(ap, const char *);\n+\t\tif (!argv[i])\n+\t\t\tbreak;\n+\t}\n+\tva_end(ap);\n+\n+\tcl_must_pass(pipe(pipe_fds));\n+\n+\tpid = fork();\n+\tif (!pid) {\n+\t\tif (dup2(pipe_fds[1], STDOUT_FILENO) < 0 ||\n+\t\t    dup2(pipe_fds[1], STDERR_FILENO) < 0 ||\n+\t\t    close(0) < 0 ||\n+\t\t    close(pipe_fds[0]) < 0 ||\n+\t\t    close(pipe_fds[1]) < 0)\n+\t\t\texit(1);\n+\n+\t\texecv(selftest_binary_path, (char **) argv);\n+\t\texit(1);\n+\t} else if (pid > 0) {\n+\t\tpid_t waited_pid;\n+\t\tchar *expected_output, *output;\n+\t\tint stat;\n+\n+\t\tcl_must_pass(close(pipe_fds[1]));\n+\n+\t\toutput = read_full(pipe_fds[0]);\n+\n+\t\twaited_pid = waitpid(pid, &stat, 0);\n+\t\tcl_assert_equal_i(pid, waited_pid);\n+\t\tcl_assert(WIFEXITED(stat));\n+\t\tcl_assert_equal_i(WEXITSTATUS(stat), expected_error_code);\n+\n+\t\texpected_output = read_file(cl_fixture(expected_output_file));\n+\t\tcl_assert_equal_s(output, expected_output);\n+\n+\t\tfree(expected_output);\n+\t\tfree(output);\n+\t} else {\n+\t\tcl_fail(\"Fork failed.\");\n+\t}\n+}\n+#endif\n+\n+void test_selftest__help(void)\n+{\n+\tcl_invoke(run(\"help\", 1, \"-h\", NULL));\n+}\n+\n+void test_selftest__without_arguments(void)\n+{\n+\tcl_invoke(run(\"without_arguments\", 10, NULL));\n+}\n+\n+void test_selftest__specific_test(void)\n+{\n+\tcl_invoke(run(\"specific_test\", 1, \"-sselftest::suite::bool\", NULL));\n+}\n+\n+void test_selftest__stop_on_failure(void)\n+{\n+\tcl_invoke(run(\"stop_on_failure\", 1, \"-Q\", NULL));\n+}\n+\n+void test_selftest__quiet(void)\n+{\n+\tcl_invoke(run(\"quiet\", 10, \"-q\", NULL));\n+}\n+\n+void test_selftest__tap(void)\n+{\n+\tcl_invoke(run(\"tap\", 10, \"-t\", NULL));\n+}\n+\n+void test_selftest__suite_names(void)\n+{\n+\tcl_invoke(run(\"suite_names\", 0, \"-l\", NULL));\n+}\n+\n+void test_selftest__summary_without_filename(void)\n+{\n+\tstruct stat st;\n+\tcl_invoke(run(\"summary_without_filename\", 10, \"-r\", NULL));\n+\t/* The summary contains timestamps, so we cannot verify its contents. */\n+\tcl_must_pass(stat(\"summary.xml\", &st));\n+}\n+\n+void test_selftest__summary_with_filename(void)\n+{\n+\tstruct stat st;\n+\tcl_invoke(run(\"summary_with_filename\", 10, \"-rdifferent.xml\", NULL));\n+\t/* The summary contains timestamps, so we cannot verify its contents. */\n+\tcl_must_pass(stat(\"different.xml\", &st));\n+}\ndiff --git a/t/unit-tests/clar/test/selftest.h b/t/unit-tests/clar/test/selftest.h\nnew file mode 100644\nindex 00000000000..220a350c504\n--- /dev/null\n+++ b/t/unit-tests/clar/test/selftest.h\n@@ -0,0 +1,3 @@\n+#include \"clar.h\"\n+\n+extern const char *selftest_binary_path;\ndiff --git a/t/unit-tests/clar/test/selftest_suite/CMakeLists.txt b/t/unit-tests/clar/test/selftest_suite/CMakeLists.txt\nnew file mode 100644\nindex 00000000000..9597d6711a7\n--- /dev/null\n+++ b/t/unit-tests/clar/test/selftest_suite/CMakeLists.txt\n@@ -0,0 +1,40 @@\n+find_package(Python COMPONENTS Interpreter REQUIRED)\n+\n+add_custom_command(OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/clar.suite\"\n+\tCOMMAND \"${Python_EXECUTABLE}\" \"${CMAKE_SOURCE_DIR}/generate.py\" --output \"${CMAKE_CURRENT_BINARY_DIR}\"\n+\tDEPENDS main.c selftest_suite.c\n+\tWORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"\n+)\n+\n+add_executable(selftest_suite)\n+set_target_properties(selftest_suite PROPERTIES\n+\tC_STANDARD 90\n+\tC_STANDARD_REQUIRED ON\n+\tC_EXTENSIONS OFF\n+)\n+\n+# MSVC generates all kinds of warnings. We may want to fix these in the future\n+# and then unconditionally treat warnings as errors.\n+if(NOT MSVC)\n+\tset_target_properties(selftest_suite PROPERTIES\n+\t\tCOMPILE_WARNING_AS_ERROR ON\n+\t)\n+endif()\n+\n+target_sources(selftest_suite PRIVATE\n+\tmain.c\n+\tselftest_suite.c\n+\t\"${CMAKE_CURRENT_BINARY_DIR}/clar.suite\"\n+)\n+target_compile_definitions(selftest_suite PRIVATE\n+\tCLAR_FIXTURE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/resources/\"\n+\tCLAR_SELFTEST\n+)\n+target_compile_options(selftest_suite PRIVATE\n+\t$<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>\n+)\n+target_include_directories(selftest_suite PRIVATE\n+\t\"${CMAKE_SOURCE_DIR}\"\n+\t\"${CMAKE_CURRENT_BINARY_DIR}\"\n+)\n+target_link_libraries(selftest_suite clar)\ndiff --git a/t/unit-tests/clar/test/selftest_suite/main.c b/t/unit-tests/clar/test/selftest_suite/main.c\nnew file mode 100644\nindex 00000000000..3ab581d3903\n--- /dev/null\n+++ b/t/unit-tests/clar/test/selftest_suite/main.c\n@@ -0,0 +1,27 @@\n+/*\n+ * Copyright (c) Vicent Marti. All rights reserved.\n+ *\n+ * This file is part of clar, distributed under the ISC license.\n+ * For full terms see the included COPYING file.\n+ */\n+\n+#include \"clar.h\"\n+\n+/*\n+ * Selftest main() for clar tests.\n+ *\n+ * You should write your own main routine for clar tests that does specific\n+ * setup and teardown as necessary for your application.  The only required\n+ * line is the call to `clar_test(argc, argv)`, which will execute the test\n+ * suite.  If you want to check the return value of the test application,\n+ * your main() should return the same value returned by clar_test().\n+ */\n+\n+#ifdef _WIN32\n+int __cdecl main(int argc, char *argv[])\n+#else\n+int main(int argc, char *argv[])\n+#endif\n+{\n+\treturn clar_test(argc, argv);\n+}\ndiff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/selftest_suite/resources/test/file\nsimilarity index 100%\nrename from t/unit-tests/clar/test/resources/test/file\nrename to t/unit-tests/clar/test/selftest_suite/resources/test/file\ndiff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/selftest_suite/selftest_suite.c\nsimilarity index 62%\nrename from t/unit-tests/clar/test/sample.c\nrename to t/unit-tests/clar/test/selftest_suite/selftest_suite.c\nindex faa1209262f..77f872128c7 100644\n--- a/t/unit-tests/clar/test/sample.c\n+++ b/t/unit-tests/clar/test/selftest_suite/selftest_suite.c\n@@ -1,6 +1,7 @@\n-#include \"clar_test.h\"\n #include <sys/stat.h>\n \n+#include \"clar.h\"\n+\n static int file_size(const char *filename)\n {\n \tstruct stat st;\n@@ -10,19 +11,14 @@ static int file_size(const char *filename)\n \treturn -1;\n }\n \n-void test_sample__initialize(void)\n-{\n-\tglobal_test_counter++;\n-}\n-\n-void test_sample__cleanup(void)\n+void test_selftest_suite__cleanup(void)\n {\n \tcl_fixture_cleanup(\"test\");\n \n \tcl_assert(file_size(\"test/file\") == -1);\n }\n \n-void test_sample__1(void)\n+void test_selftest_suite__1(void)\n {\n \tcl_assert(1);\n \tcl_must_pass(0);  /* 0 == success */\n@@ -30,7 +26,7 @@ void test_sample__1(void)\n \tcl_must_pass(-1); /* demonstrate a failing call */\n }\n \n-void test_sample__2(void)\n+void test_selftest_suite__2(void)\n {\n \tcl_fixture_sandbox(\"test\");\n \n@@ -39,7 +35,7 @@ void test_sample__2(void)\n \tcl_assert(100 == 101);\n }\n \n-void test_sample__strings(void)\n+void test_selftest_suite__strings(void)\n {\n \tconst char *actual = \"expected\";\n \tcl_assert_equal_s(\"expected\", actual);\n@@ -47,7 +43,7 @@ void test_sample__strings(void)\n \tcl_assert_equal_s_(\"mismatched\", actual, \"this one fails\");\n }\n \n-void test_sample__strings_with_length(void)\n+void test_selftest_suite__strings_with_length(void)\n {\n \tconst char *actual = \"expected\";\n \tcl_assert_equal_strn(\"expected_\", actual, 8);\n@@ -56,29 +52,41 @@ void test_sample__strings_with_length(void)\n \tcl_assert_equal_strn_(\"exactly\", actual, 3, \"this one fails\");\n }\n \n-void test_sample__int(void)\n+void test_selftest_suite__int(void)\n {\n \tint value = 100;\n \tcl_assert_equal_i(100, value);\n \tcl_assert_equal_i_(101, value, \"extra note on failing test\");\n }\n \n-void test_sample__int_fmt(void)\n+void test_selftest_suite__int_fmt(void)\n {\n \tint value = 100;\n \tcl_assert_equal_i_fmt(022, value, \"%04o\");\n }\n \n-void test_sample__bool(void)\n+void test_selftest_suite__bool(void)\n {\n \tint value = 100;\n \tcl_assert_equal_b(1, value);       /* test equality as booleans */\n \tcl_assert_equal_b(0, value);\n }\n \n-void test_sample__ptr(void)\n+void test_selftest_suite__ptr(void)\n {\n-\tconst char *actual = \"expected\";\n-\tcl_assert_equal_p(actual, actual); /* pointers to same object */\n-\tcl_assert_equal_p(&actual, actual);\n+\tvoid *p1 = (void *)0x1, *p2 = (void *)0x2;\n+\tcl_assert_equal_p(p1, p1); /* pointers to same object */\n+\tcl_assert_equal_p(p1, p2);\n+}\n+\n+void test_selftest_suite__multiline_description(void)\n+{\n+\tcl_must_pass_(-1, \"description line 1\\ndescription line 2\");\n+}\n+\n+void test_selftest_suite__null_string(void)\n+{\n+\tconst char *actual = NULL;\n+\tcl_assert_equal_s(actual, actual);\n+\tcl_assert_equal_s_(\"expected\", actual, \"this one fails\");\n }\n\n---\nbase-commit: 4975ec3473b4bc61bc8a3df1ef29d0b7e7959e87\nchange-id: 20250909-b4-pks-clar-update-4d934e2a6390\n\n"},{"id":"526134","messageId":"xmqqikhpotto.fsf@gitster.g","threadId":"64118","inReplyTo":"20250910-b4-pks-clar-update-v1-1-26a196237e0a@pks.im","subject":"Re: [PATCH] t/unit-tests: update clar to fcbed04","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-11T16:08:35Z","receivedAt":"2025-09-11T16:08:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Junio, I was wondering whether we also want to do this similarly to how\n> you handle the gitk/git-gui pull requests. I ultimately don't mind it\n> much though, so I'm also happy to do this with \"normal\" patch series.\n\nI do not think the \"git merge -Xsubtree=$directory $URL\" merges have\ngivem us much benefit as we originally hoped.  Even though we have\nfull history of the other projects' theoretically, digging something\nout of that history has not been as seamless process as it could.  I\nalways ended up doing things in a two-step fashion, i.e.\n\n (1) ask \"log --first-parent\" the last commit that touched the\n     -Xsubtree=$directory, then\n\n (2) dig from the second parent of that commit.  This is another\n     reason why I do not like to *move* the -Xsubtree=$directory\n     without a good rationale.\n\nSo I wouldn't particularly recommend it.\n\nThanks.\n"}]}