]> git.ipfire.org Git - thirdparty/git.git/commitdiff
t/unit-tests: update clar to fcbed04
authorPatrick Steinhardt <ps@pks.im>
Wed, 10 Sep 2025 13:09:58 +0000 (15:09 +0200)
committerJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2025 16:08:49 +0000 (09:08 -0700)
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>
32 files changed:
t/unit-tests/clar/.github/workflows/ci.yml
t/unit-tests/clar/CMakeLists.txt
t/unit-tests/clar/README.md
t/unit-tests/clar/clar.c
t/unit-tests/clar/clar.h
t/unit-tests/clar/clar/fixtures.h
t/unit-tests/clar/clar/fs.h
t/unit-tests/clar/clar/print.h
t/unit-tests/clar/clar/sandbox.h
t/unit-tests/clar/clar/summary.h
t/unit-tests/clar/example/CMakeLists.txt [new file with mode: 0644]
t/unit-tests/clar/example/example.c [new file with mode: 0644]
t/unit-tests/clar/example/main.c [moved from t/unit-tests/clar/test/main.c.sample with 96% similarity]
t/unit-tests/clar/test/CMakeLists.txt
t/unit-tests/clar/test/clar_test.h [deleted file]
t/unit-tests/clar/test/expected/help [new file with mode: 0644]
t/unit-tests/clar/test/expected/quiet [new file with mode: 0644]
t/unit-tests/clar/test/expected/specific_test [new file with mode: 0644]
t/unit-tests/clar/test/expected/stop_on_failure [new file with mode: 0644]
t/unit-tests/clar/test/expected/suite_names [new file with mode: 0644]
t/unit-tests/clar/test/expected/summary.xml [new file with mode: 0644]
t/unit-tests/clar/test/expected/summary_with_filename [new file with mode: 0644]
t/unit-tests/clar/test/expected/summary_without_filename [new file with mode: 0644]
t/unit-tests/clar/test/expected/tap [new file with mode: 0644]
t/unit-tests/clar/test/expected/without_arguments [new file with mode: 0644]
t/unit-tests/clar/test/main.c
t/unit-tests/clar/test/selftest.c [new file with mode: 0644]
t/unit-tests/clar/test/selftest.h [new file with mode: 0644]
t/unit-tests/clar/test/selftest_suite/CMakeLists.txt [new file with mode: 0644]
t/unit-tests/clar/test/selftest_suite/main.c [new file with mode: 0644]
t/unit-tests/clar/test/selftest_suite/resources/test/file [moved from t/unit-tests/clar/test/resources/test/file with 100% similarity]
t/unit-tests/clar/test/selftest_suite/selftest_suite.c [moved from t/unit-tests/clar/test/sample.c with 62% similarity]

index 0065843d17aa8ba509c59b2f94bb8a7f864b3517..c41f55f6ff5befbf7b0e6c6d175bcfa4fdffe760 100644 (file)
@@ -13,6 +13,11 @@ jobs:
         platform:
           - os: ubuntu-latest
             generator: Unix Makefiles
+          - os: ubuntu-latest
+            generator: Unix Makefiles
+            env:
+              CC: "clang"
+              CFLAGS: "-fsanitize=leak"
           - os: macos-latest
             generator: Unix Makefiles
           - os: windows-latest
@@ -21,15 +26,26 @@ jobs:
             generator: MSYS Makefiles
           - os: windows-latest
             generator: MinGW Makefiles
+      fail-fast: false
 
     runs-on: ${{ matrix.platform.os }}
 
+    env:
+      CC: ${{matrix.platform.env.CC}}
+      CFLAGS: ${{matrix.platform.env.CFLAGS}}
+
     steps:
     - name: Check out
       uses: actions/checkout@v2
     - name: Build
+      shell: bash
       run: |
         mkdir build
         cd build
         cmake .. -G "${{matrix.platform.generator}}"
-        cmake --build .
+        cmake --build . --verbose
+    - name: Test
+      shell: bash
+      run: |
+        cd build
+        CTEST_OUTPUT_ON_FAILURE=1 ctest --build-config Debug
index 12d4af114fe3b332fce9b5db192a2a7978756681..125db05bc10ca47b2c5ab6107e643a45ee8a819c 100644 (file)
@@ -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()
index a8961c5f10f25e665d00fa954affbf4601f3fc49..41595989ca6ed877751b3c1eda4bf06f6bf55354 100644 (file)
@@ -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
index 03a3aa8e873bfcb1d15f19b5f168edc323ca2785..80c5359425213adae40b54bc429ea054397628dd 100644 (file)
@@ -79,6 +79,8 @@
 #      else
 #              define p_snprintf snprintf
 #      endif
+
+#      define localtime_r(timer, buf) (localtime_s(buf, timer) == 0 ? buf : NULL)
 #else
 #      include <sys/wait.h> /* waitpid(2) */
 #      include <unistd.h>
@@ -150,7 +152,6 @@ static struct {
 
        enum cl_output_format output_format;
 
-       int report_errors_only;
        int exit_on_error;
        int verbosity;
 
@@ -164,6 +165,10 @@ static struct {
        struct clar_report *reports;
        struct clar_report *last_report;
 
+       const char *invoke_file;
+       const char *invoke_func;
+       size_t invoke_line;
+
        void (*local_cleanup)(void *);
        void *local_cleanup_payload;
 
@@ -199,8 +204,10 @@ static void clar_print_onabortv(const char *msg, va_list argp);
 static void clar_print_onabort(const char *msg, ...);
 
 /* From clar_sandbox.c */
-static void clar_unsandbox(void);
-static void clar_sandbox(void);
+static void clar_tempdir_init(void);
+static void clar_tempdir_shutdown(void);
+static int clar_sandbox_create(const char *suite_name, const char *test_name);
+static int clar_sandbox_cleanup(void);
 
 /* From summary.h */
 static struct clar_summary *clar_summary_init(const char *filename);
@@ -304,6 +311,8 @@ clar_run_test(
 
        CL_TRACE(CL_TRACE__TEST__BEGIN);
 
+       clar_sandbox_create(suite->name, test->name);
+
        _clar.last_report->start = time(NULL);
        clar_time_now(&start);
 
@@ -328,9 +337,13 @@ clar_run_test(
        if (_clar.local_cleanup != NULL)
                _clar.local_cleanup(_clar.local_cleanup_payload);
 
+       clar__clear_invokepoint();
+
        if (cleanup->ptr != NULL)
                cleanup->ptr();
 
+       clar_sandbox_cleanup();
+
        CL_TRACE(CL_TRACE__TEST__END);
 
        _clar.tests_ran++;
@@ -339,11 +352,7 @@ clar_run_test(
        _clar.local_cleanup = NULL;
        _clar.local_cleanup_payload = NULL;
 
-       if (_clar.report_errors_only) {
-               clar_report_errors(_clar.last_report);
-       } else {
-               clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);
-       }
+       clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);
 }
 
 static void
@@ -360,8 +369,7 @@ clar_run_suite(const struct clar_suite *suite, const char *filter)
        if (_clar.exit_on_error && _clar.total_errors)
                return;
 
-       if (!_clar.report_errors_only)
-               clar_print_onsuite(suite->name, ++_clar.suites_ran);
+       clar_print_onsuite(suite->name, ++_clar.suites_ran);
 
        _clar.active_suite = suite->name;
        _clar.active_test = NULL;
@@ -428,12 +436,12 @@ clar_usage(const char *arg)
        printf("  -iname        Include the suite with `name`\n");
        printf("  -xname        Exclude the suite with `name`\n");
        printf("  -v            Increase verbosity (show suite names)\n");
-       printf("  -q            Only report tests that had an error\n");
+       printf("  -q            Decrease verbosity, inverse to -v\n");
        printf("  -Q            Quit as soon as a test fails\n");
        printf("  -t            Display results in tap format\n");
        printf("  -l            Print suite names\n");
        printf("  -r[filename]  Write summary file (to the optional filename)\n");
-       exit(-1);
+       exit(1);
 }
 
 static void
@@ -441,18 +449,11 @@ clar_parse_args(int argc, char **argv)
 {
        int i;
 
-       /* Verify options before execute */
        for (i = 1; i < argc; ++i) {
                char *argument = argv[i];
 
-               if (argument[0] != '-' || argument[1] == '\0'
-                   || strchr("sixvqQtlr", argument[1]) == NULL) {
+               if (argument[0] != '-' || argument[1] == '\0')
                        clar_usage(argv[0]);
-               }
-       }
-
-       for (i = 1; i < argc; ++i) {
-               char *argument = argv[i];
 
                switch (argument[1]) {
                case 's':
@@ -465,8 +466,13 @@ clar_parse_args(int argc, char **argv)
                        argument += offset;
                        arglen = strlen(argument);
 
-                       if (arglen == 0)
-                               clar_usage(argv[0]);
+                       if (arglen == 0) {
+                               if (i + 1 == argc)
+                                       clar_usage(argv[0]);
+
+                               argument = argv[++i];
+                               arglen = strlen(argument);
+                       }
 
                        for (j = 0; j < _clar_suite_count; ++j) {
                                suitelen = strlen(_clar_suites[j].name);
@@ -483,9 +489,6 @@ clar_parse_args(int argc, char **argv)
 
                                        ++found;
 
-                                       if (!exact)
-                                               _clar.verbosity = MAX(_clar.verbosity, 1);
-
                                        switch (action) {
                                        case 's': {
                                                struct clar_explicit *explicit;
@@ -517,23 +520,37 @@ clar_parse_args(int argc, char **argv)
 
                        if (!found)
                                clar_abort("No suite matching '%s' found.\n", argument);
+
                        break;
                }
 
                case 'q':
-                       _clar.report_errors_only = 1;
+                       if (argument[2] != '\0')
+                               clar_usage(argv[0]);
+
+                       _clar.verbosity--;
                        break;
 
                case 'Q':
+                       if (argument[2] != '\0')
+                               clar_usage(argv[0]);
+
                        _clar.exit_on_error = 1;
                        break;
 
                case 't':
+                       if (argument[2] != '\0')
+                               clar_usage(argv[0]);
+
                        _clar.output_format = CL_OUTPUT_TAP;
                        break;
 
                case 'l': {
                        size_t j;
+
+                       if (argument[2] != '\0')
+                               clar_usage(argv[0]);
+
                        printf("Test suites (use -s<name> to run just one):\n");
                        for (j = 0; j < _clar_suite_count; ++j)
                                printf(" %3d: %s\n", (int)j, _clar_suites[j].name);
@@ -542,23 +559,27 @@ clar_parse_args(int argc, char **argv)
                }
 
                case 'v':
+                       if (argument[2] != '\0')
+                               clar_usage(argv[0]);
+
                        _clar.verbosity++;
                        break;
 
                case 'r':
                        _clar.write_summary = 1;
                        free(_clar.summary_filename);
+
                        if (*(argument + 2)) {
                                if ((_clar.summary_filename = strdup(argument + 2)) == NULL)
                                        clar_abort("Failed to allocate summary filename.\n");
                        } else {
                                _clar.summary_filename = NULL;
                        }
+
                        break;
 
                default:
-                       clar_abort("Unexpected commandline argument '%s'.\n",
-                                  argument[1]);
+                       clar_usage(argv[0]);
                }
        }
 }
@@ -591,7 +612,7 @@ clar_test_init(int argc, char **argv)
        if (_clar.write_summary)
            _clar.summary = clar_summary_init(_clar.summary_filename);
 
-       clar_sandbox();
+       clar_tempdir_init();
 }
 
 int
@@ -623,7 +644,7 @@ clar_test_shutdown(void)
                _clar.total_errors
        );
 
-       clar_unsandbox();
+       clar_tempdir_shutdown();
 
        if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0)
                clar_abort("Failed to write the summary file '%s: %s.\n",
@@ -635,6 +656,14 @@ clar_test_shutdown(void)
        }
 
        for (report = _clar.reports; report; report = report_next) {
+               struct clar_error *error, *error_next;
+
+               for (error = report->errors; error; error = error_next) {
+                       free(error->description);
+                       error_next = error->next;
+                       free(error);
+               }
+
                report_next = report->next;
                free(report);
        }
@@ -660,7 +689,7 @@ static void abort_test(void)
                clar_print_onabort(
                                "Fatal error: a cleanup method raised an exception.\n");
                clar_report_errors(_clar.last_report);
-               exit(-1);
+               exit(1);
        }
 
        CL_TRACE(CL_TRACE__TEST__LONGJMP);
@@ -695,9 +724,9 @@ void clar__fail(
 
        _clar.last_report->last_error = error;
 
-       error->file = file;
-       error->function = function;
-       error->line_number = line;
+       error->file = _clar.invoke_file ? _clar.invoke_file : file;
+       error->function = _clar.invoke_func ? _clar.invoke_func : function;
+       error->line_number = _clar.invoke_line ? _clar.invoke_line : line;
        error->error_msg = error_msg;
 
        if (description != NULL &&
@@ -754,7 +783,12 @@ void clar__assert_equal(
                                p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
                                        s1, s2, pos);
                        } else {
-                               p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+                               const char *q1 = s1 ? "'" : "";
+                               const char *q2 = s2 ? "'" : "";
+                               s1 = s1 ? s1 : "NULL";
+                               s2 = s2 ? s2 : "NULL";
+                               p_snprintf(buf, sizeof(buf), "%s%s%s != %s%s%s",
+                                          q1, s1, q1, q2, s2, q2);
                        }
                }
        }
@@ -767,12 +801,17 @@ void clar__assert_equal(
                if (!is_equal) {
                        if (s1 && s2) {
                                int pos;
-                               for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos)
+                               for (pos = 0; pos < len && s1[pos] == s2[pos]; ++pos)
                                        /* find differing byte offset */;
                                p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)",
                                        len, s1, len, s2, pos);
                        } else {
-                               p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2);
+                               const char *q1 = s1 ? "'" : "";
+                               const char *q2 = s2 ? "'" : "";
+                               s1 = s1 ? s1 : "NULL";
+                               s2 = s2 ? s2 : "NULL";
+                               p_snprintf(buf, sizeof(buf), "%s%.*s%s != %s%.*s%s",
+                                          q1, len, s1, q1, q2, len, s2, q2);
                        }
                }
        }
@@ -790,7 +829,12 @@ void clar__assert_equal(
                                p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
                                        wcs1, wcs2, pos);
                        } else {
-                               p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+                               const char *q1 = wcs1 ? "'" : "";
+                               const char *q2 = wcs2 ? "'" : "";
+                               wcs1 = wcs1 ? wcs1 : L"NULL";
+                               wcs2 = wcs2 ? wcs2 : L"NULL";
+                               p_snprintf(buf, sizeof(buf), "%s%ls%s != %s%ls%s",
+                                          q1, wcs1, q1, q2, wcs2, q2);
                        }
                }
        }
@@ -803,12 +847,17 @@ void clar__assert_equal(
                if (!is_equal) {
                        if (wcs1 && wcs2) {
                                int pos;
-                               for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos)
+                               for (pos = 0; pos < len && wcs1[pos] == wcs2[pos]; ++pos)
                                        /* find differing byte offset */;
                                p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)",
                                        len, wcs1, len, wcs2, pos);
                        } else {
-                               p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2);
+                               const char *q1 = wcs1 ? "'" : "";
+                               const char *q2 = wcs2 ? "'" : "";
+                               wcs1 = wcs1 ? wcs1 : L"NULL";
+                               wcs2 = wcs2 ? wcs2 : L"NULL";
+                               p_snprintf(buf, sizeof(buf), "%s%.*ls%s != %s%.*ls%s",
+                                          q1, len, wcs1, q1, q2, len, wcs2, q2);
                        }
                }
        }
@@ -826,7 +875,8 @@ void clar__assert_equal(
                void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *);
                is_equal = (p1 == p2);
                if (!is_equal)
-                       p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2);
+                       p_snprintf(buf, sizeof(buf), "0x%"PRIxPTR" != 0x%"PRIxPTR,
+                                  (uintptr_t)p1, (uintptr_t)p2);
        }
        else {
                int i1 = va_arg(args, int), i2 = va_arg(args, int);
@@ -850,6 +900,23 @@ void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
        _clar.local_cleanup_payload = opaque;
 }
 
+void clar__set_invokepoint(
+       const char *file,
+       const char *func,
+       size_t line)
+{
+       _clar.invoke_file = file;
+       _clar.invoke_func = func;
+       _clar.invoke_line = line;
+}
+
+void clar__clear_invokepoint(void)
+{
+       _clar.invoke_file = NULL;
+       _clar.invoke_func = NULL;
+       _clar.invoke_line = 0;
+}
+
 #include "clar/sandbox.h"
 #include "clar/fixtures.h"
 #include "clar/fs.h"
index 8c22382bd56e905fc539bb05f69860da469ebc6d..ca72292ae918da71d2b0025f1ff3e4759b2ae98f 100644 (file)
@@ -8,6 +8,25 @@
 #define __CLAR_TEST_H__
 
 #include <stdlib.h>
+#include <limits.h>
+
+#if defined(_WIN32) && defined(CLAR_WIN32_LONGPATHS)
+# define CLAR_MAX_PATH 4096
+#elif defined(_WIN32)
+# define CLAR_MAX_PATH MAX_PATH
+#else
+# define CLAR_MAX_PATH PATH_MAX
+#endif
+
+#ifndef CLAR_SELFTEST
+# define CLAR_CURRENT_FILE __FILE__
+# define CLAR_CURRENT_LINE __LINE__
+# define CLAR_CURRENT_FUNC __func__
+#else
+# define CLAR_CURRENT_FILE "file"
+# define CLAR_CURRENT_LINE 42
+# define CLAR_CURRENT_FUNC "func"
+#endif
 
 enum cl_test_status {
        CL_TEST_OK,
@@ -30,6 +49,7 @@ void clar_test_shutdown(void);
 int clar_test(int argc, char *argv[]);
 
 const char *clar_sandbox_path(void);
+const char *clar_tempdir_path(void);
 
 void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
 void cl_fs_cleanup(void);
@@ -83,19 +103,33 @@ void cl_fixture_cleanup(const char *fixture_name);
 const char *cl_fixture_basename(const char *fixture_name);
 #endif
 
+/**
+ * Invoke a helper function, which itself will use `cl_assert`
+ * constructs. This will preserve the stack information of the
+ * current call point, so that function name and line number
+ * information is shown from the line of the test, instead of
+ * the helper function.
+ */
+#define cl_invoke(expr) \
+       do { \
+               clar__set_invokepoint(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE); \
+               expr; \
+               clar__clear_invokepoint(); \
+       } while(0)
+
 /**
  * Assertion macros with explicit error message
  */
-#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1)
-#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1)
-#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1)
+#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)
+#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)
+#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)
 
 /**
  * Check macros with explicit error message
  */
-#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0)
-#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0)
-#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0)
+#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)
+#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)
+#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)
 
 /**
  * Assertion macros with no error message
@@ -114,33 +148,33 @@ const char *cl_fixture_basename(const char *fixture_name);
 /**
  * Forced failure/warning
  */
-#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1)
-#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0)
+#define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Test failed.", desc, 1)
+#define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Warning during test execution:", desc, 0)
 
 #define cl_skip() clar__skip()
 
 /**
  * Typed assertion macros
  */
-#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
-#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
+#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))
+#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))
 
-#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
-#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
+#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))
+#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))
 
-#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
-#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))
+#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))
+#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))
 
-#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
-#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))
+#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))
+#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))
 
-#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
-#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
-#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
+#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))
+#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))
+#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))
 
-#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
+#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))
 
-#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
+#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))
 
 void clar__skip(void);
 
@@ -170,4 +204,11 @@ void clar__assert_equal(
        const char *fmt,
        ...);
 
+void clar__set_invokepoint(
+       const char *file,
+       const char *func,
+       size_t line);
+
+void clar__clear_invokepoint(void);
+
 #endif
index 6ec6423484de995a717edf5f492ed1de6a55e22d..9f1023df594462b072ca196b57389d1870bf682d 100644 (file)
@@ -2,7 +2,7 @@
 static const char *
 fixture_path(const char *base, const char *fixture_name)
 {
-       static char _path[4096];
+       static char _path[CLAR_MAX_PATH];
        size_t root_len;
 
        root_len = strlen(base);
@@ -28,7 +28,7 @@ const char *cl_fixture(const char *fixture_name)
 
 void cl_fixture_sandbox(const char *fixture_name)
 {
-       fs_copy(cl_fixture(fixture_name), _clar_path);
+       fs_copy(cl_fixture(fixture_name), clar_sandbox_path());
 }
 
 const char *cl_fixture_basename(const char *fixture_name)
@@ -45,6 +45,6 @@ const char *cl_fixture_basename(const char *fixture_name)
 
 void cl_fixture_cleanup(const char *fixture_name)
 {
-       fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+       fs_rm(fixture_path(clar_sandbox_path(), cl_fixture_basename(fixture_name)));
 }
 #endif
index 2203743fb480467c73b037f3205e364e9a0f0d77..f1311d91e8503e0b801395c83c1590ad7c48eb27 100644 (file)
@@ -8,12 +8,6 @@
 
 #ifdef _WIN32
 
-#ifdef CLAR_WIN32_LONGPATHS
-# define CLAR_MAX_PATH 4096
-#else
-# define CLAR_MAX_PATH MAX_PATH
-#endif
-
 #define RM_RETRY_COUNT 5
 #define RM_RETRY_DELAY 10
 
@@ -296,7 +290,7 @@ void
 cl_fs_cleanup(void)
 {
 #ifdef CLAR_FIXTURE_PATH
-       fs_rm(fixture_path(_clar_path, "*"));
+       fs_rm(fixture_path(clar_tempdir_path(), "*"));
 #else
        ((void)fs_copy); /* unused */
 #endif
@@ -371,17 +365,19 @@ static void
 fs_copydir_helper(const char *source, const char *dest, int dest_mode)
 {
        DIR *source_dir;
-       struct dirent *d;
 
        mkdir(dest, dest_mode);
 
        cl_assert_(source_dir = opendir(source), "Could not open source dir");
-       for (;;) {
+       while (1) {
+               struct dirent *d;
                char *child;
 
                errno = 0;
-               if ((d = readdir(source_dir)) == NULL)
+               d = readdir(source_dir);
+               if (!d)
                        break;
+
                if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
                        continue;
 
@@ -479,15 +475,18 @@ static void
 fs_rmdir_helper(const char *path)
 {
        DIR *dir;
-       struct dirent *d;
 
        cl_assert_(dir = opendir(path), "Could not open dir");
-       for (;;) {
+
+       while (1) {
+               struct dirent *d;
                char *child;
 
                errno = 0;
-               if ((d = readdir(dir)) == NULL)
+               d = readdir(dir);
+               if (!d)
                        break;
+
                if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
                        continue;
 
@@ -524,7 +523,7 @@ fs_rm(const char *path)
 void
 cl_fs_cleanup(void)
 {
-       clar_unsandbox();
-       clar_sandbox();
+       clar_tempdir_shutdown();
+       clar_tempdir_init();
 }
 #endif
index 69d0ee967e7475c5a9505400c277c7408556580e..0282aaa13810d8acc121d566fd0285a2893a2089 100644 (file)
@@ -3,6 +3,10 @@
 static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
 {
        (void)test_count;
+
+       if (_clar.verbosity < 0)
+               return;
+
        printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
        printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
 }
@@ -13,10 +17,27 @@ static void clar_print_clap_shutdown(int test_count, int suite_count, int error_
        (void)suite_count;
        (void)error_count;
 
-       printf("\n\n");
+       if (_clar.verbosity >= 0)
+               printf("\n\n");
        clar_report_all();
 }
 
+
+static void clar_print_indented(const char *str, int indent)
+{
+       const char *bol, *eol;
+
+       for (bol = str; *bol; bol = eol) {
+               eol = strchr(bol, '\n');
+               if (eol)
+                       eol++;
+               else
+                       eol = bol + strlen(bol);
+               printf("%*s%.*s", indent, "", (int)(eol - bol), bol);
+       }
+       putc('\n', stdout);
+}
+
 static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
 {
        printf("  %d) Failure:\n", num);
@@ -27,10 +48,10 @@ static void clar_print_clap_error(int num, const struct clar_report *report, con
                error->file,
                error->line_number);
 
-       printf("  %s\n", error->error_msg);
+       clar_print_indented(error->error_msg, 2);
 
        if (error->description != NULL)
-               printf("  %s\n", error->description);
+               clar_print_indented(error->description, 2);
 
        printf("\n");
        fflush(stdout);
@@ -41,14 +62,17 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name
        (void)test_name;
        (void)test_number;
 
+       if (_clar.verbosity < 0)
+               return;
+
        if (_clar.verbosity > 1) {
                printf("%s::%s: ", suite_name, test_name);
 
                switch (status) {
                case CL_TEST_OK: printf("ok\n"); break;
                case CL_TEST_FAILURE: printf("fail\n"); break;
-               case CL_TEST_SKIP: printf("skipped"); break;
-               case CL_TEST_NOTRUN: printf("notrun"); break;
+               case CL_TEST_SKIP: printf("skipped\n"); break;
+               case CL_TEST_NOTRUN: printf("notrun\n"); break;
                }
        } else {
                switch (status) {
@@ -64,6 +88,8 @@ static void clar_print_clap_ontest(const char *suite_name, const char *test_name
 
 static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
 {
+       if (_clar.verbosity < 0)
+               return;
        if (_clar.verbosity == 1)
                printf("\n%s", suite_name);
 
@@ -127,18 +153,20 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,
        case CL_TEST_FAILURE:
                printf("not ok %d - %s::%s\n", test_number, suite_name, test_name);
 
-               printf("    ---\n");
-               printf("    reason: |\n");
-               printf("      %s\n", error->error_msg);
+               if (_clar.verbosity >= 0) {
+                       printf("    ---\n");
+                       printf("    reason: |\n");
+                       clar_print_indented(error->error_msg, 6);
 
-               if (error->description)
-                       printf("      %s\n", error->description);
+                       if (error->description)
+                               clar_print_indented(error->description, 6);
 
-               printf("    at:\n");
-               printf("      file: '"); print_escaped(error->file); printf("'\n");
-               printf("      line: %" PRIuMAX "\n", error->line_number);
-               printf("      function: '%s'\n", error->function);
-               printf("    ---\n");
+                       printf("    at:\n");
+                       printf("      file: '"); print_escaped(error->file); printf("'\n");
+                       printf("      line: %" PRIuMAX "\n", error->line_number);
+                       printf("      function: '%s'\n", error->function);
+                       printf("    ---\n");
+               }
 
                break;
        case CL_TEST_SKIP:
@@ -152,6 +180,8 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name,
 
 static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
 {
+       if (_clar.verbosity < 0)
+               return;
        printf("# start of suite %d: %s\n", suite_index, suite_name);
 }
 
index bc960f50e0f2ec41f5eb0b1e052e2aac44ce460c..52add8acebaea49e864fb27092551e9feb724933 100644 (file)
@@ -2,7 +2,17 @@
 #include <sys/syslimits.h>
 #endif
 
-static char _clar_path[4096 + 1];
+/*
+ * The tempdir is the temporary directory for the entirety of the clar
+ * process execution. The sandbox is an individual temporary directory
+ * for the execution of an individual test. Sandboxes are deleted
+ * entirely after test execution to avoid pollution across tests.
+ */
+
+static char _clar_tempdir[CLAR_MAX_PATH];
+static size_t _clar_tempdir_len;
+
+static char _clar_sandbox[CLAR_MAX_PATH];
 
 static int
 is_valid_tmp_path(const char *path)
@@ -15,7 +25,10 @@ is_valid_tmp_path(const char *path)
        if (!S_ISDIR(st.st_mode))
                return 0;
 
-       return (access(path, W_OK) == 0);
+       if (access(path, W_OK) != 0)
+               return 0;
+
+       return (strlen(path) < CLAR_MAX_PATH);
 }
 
 static int
@@ -31,14 +44,11 @@ find_tmp_path(char *buffer, size_t length)
 
        for (i = 0; i < var_count; ++i) {
                const char *env = getenv(env_vars[i]);
+
                if (!env)
                        continue;
 
                if (is_valid_tmp_path(env)) {
-#ifdef __APPLE__
-                       if (length >= PATH_MAX && realpath(env, buffer) != NULL)
-                               return 0;
-#endif
                        strncpy(buffer, env, length - 1);
                        buffer[length - 1] = '\0';
                        return 0;
@@ -47,21 +57,18 @@ find_tmp_path(char *buffer, size_t length)
 
        /* If the environment doesn't say anything, try to use /tmp */
        if (is_valid_tmp_path("/tmp")) {
-#ifdef __APPLE__
-               if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL)
-                       return 0;
-#endif
                strncpy(buffer, "/tmp", length - 1);
                buffer[length - 1] = '\0';
                return 0;
        }
 
 #else
-       DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
-       if (env_len > 0 && env_len < (DWORD)length)
+       DWORD len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
+       if (len > 0 && len < (DWORD)length)
                return 0;
 
-       if (GetTempPath((DWORD)length, buffer))
+       len = GetTempPath((DWORD)length, buffer);
+       if (len > 0 && len < (DWORD)length)
                return 0;
 #endif
 
@@ -75,17 +82,53 @@ find_tmp_path(char *buffer, size_t length)
        return -1;
 }
 
-static void clar_unsandbox(void)
+static int canonicalize_tmp_path(char *buffer)
+{
+#ifdef _WIN32
+       char tmp[CLAR_MAX_PATH], *p;
+       DWORD ret;
+
+       ret = GetFullPathName(buffer, CLAR_MAX_PATH, tmp, NULL);
+
+       if (ret == 0 || ret > CLAR_MAX_PATH)
+               return -1;
+
+       ret = GetLongPathName(tmp, buffer, CLAR_MAX_PATH);
+
+       if (ret == 0 || ret > CLAR_MAX_PATH)
+               return -1;
+
+       /* normalize path to POSIX forward slashes */
+       for (p = buffer; *p; p++)
+               if (*p == '\\')
+                       *p = '/';
+
+       return 0;
+#elif defined(CLAR_HAS_REALPATH)
+       char tmp[CLAR_MAX_PATH];
+
+       if (realpath(buffer, tmp) == NULL)
+               return -1;
+
+       strcpy(buffer, tmp);
+       return 0;
+#else
+       (void)buffer;
+       return 0;
+#endif
+}
+
+static void clar_tempdir_shutdown(void)
 {
-       if (_clar_path[0] == '\0')
+       if (_clar_tempdir[0] == '\0')
                return;
 
        cl_must_pass(chdir(".."));
 
-       fs_rm(_clar_path);
+       fs_rm(_clar_tempdir);
 }
 
-static int build_sandbox_path(void)
+static int build_tempdir_path(void)
 {
 #ifdef CLAR_TMPDIR
        const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
@@ -95,64 +138,153 @@ static int build_sandbox_path(void)
 
        size_t len;
 
-       if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+       if (find_tmp_path(_clar_tempdir, sizeof(_clar_tempdir)) < 0 ||
+           canonicalize_tmp_path(_clar_tempdir) < 0)
                return -1;
 
-       len = strlen(_clar_path);
+       len = strlen(_clar_tempdir);
 
-#ifdef _WIN32
-       { /* normalize path to POSIX forward slashes */
-               size_t i;
-               for (i = 0; i < len; ++i) {
-                       if (_clar_path[i] == '\\')
-                               _clar_path[i] = '/';
-               }
-       }
-#endif
+       if (len + strlen(path_tail) + 2 > CLAR_MAX_PATH)
+               return -1;
 
-       if (_clar_path[len - 1] != '/') {
-               _clar_path[len++] = '/';
-       }
+       if (_clar_tempdir[len - 1] != '/')
+               _clar_tempdir[len++] = '/';
 
-       strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+       strncpy(_clar_tempdir + len, path_tail, sizeof(_clar_tempdir) - len);
 
 #if defined(__MINGW32__)
-       if (_mktemp(_clar_path) == NULL)
+       if (_mktemp(_clar_tempdir) == NULL)
                return -1;
 
-       if (mkdir(_clar_path, 0700) != 0)
+       if (mkdir(_clar_tempdir, 0700) != 0)
                return -1;
 #elif defined(_WIN32)
-       if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+       if (_mktemp_s(_clar_tempdir, sizeof(_clar_tempdir)) != 0)
                return -1;
 
-       if (mkdir(_clar_path, 0700) != 0)
+       if (mkdir(_clar_tempdir, 0700) != 0)
                return -1;
-#elif defined(__sun) || defined(__TANDEM)
-       if (mktemp(_clar_path) == NULL)
+#elif defined(__sun) || defined(__TANDEM) || defined(__hpux)
+       if (mktemp(_clar_tempdir) == NULL)
                return -1;
 
-       if (mkdir(_clar_path, 0700) != 0)
+       if (mkdir(_clar_tempdir, 0700) != 0)
                return -1;
 #else
-       if (mkdtemp(_clar_path) == NULL)
+       if (mkdtemp(_clar_tempdir) == NULL)
                return -1;
 #endif
 
+       _clar_tempdir_len = strlen(_clar_tempdir);
        return 0;
 }
 
-static void clar_sandbox(void)
+static void clar_tempdir_init(void)
 {
-       if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
-               clar_abort("Failed to build sandbox path.\n");
+       if (_clar_tempdir[0] == '\0' && build_tempdir_path() < 0)
+               clar_abort("Failed to build tempdir path.\n");
 
-       if (chdir(_clar_path) != 0)
-               clar_abort("Failed to change into sandbox directory '%s': %s.\n",
-                          _clar_path, strerror(errno));
+       if (chdir(_clar_tempdir) != 0)
+               clar_abort("Failed to change into tempdir '%s': %s.\n",
+                          _clar_tempdir, strerror(errno));
+
+#if !defined(CLAR_SANDBOX_TEST_NAMES) && defined(_WIN32)
+       srand(clock() ^ (unsigned int)time(NULL) ^ GetCurrentProcessId() ^ GetCurrentThreadId());
+#elif !defined(CLAR_SANDBOX_TEST_NAMES)
+       srand(clock() ^ time(NULL) ^ ((unsigned)getpid() << 16));
+#endif
+}
+
+static void append(char *dst, const char *src)
+{
+       char *d;
+       const char *s;
+
+       for (d = dst; *d; d++)
+               ;
+
+       for (s = src; *s; d++, s++)
+               if (*s == ':')
+                       *d = '_';
+               else
+                       *d = *s;
+
+       *d = '\0';
+}
+
+static int clar_sandbox_create(const char *suite_name, const char *test_name)
+{
+#ifndef CLAR_SANDBOX_TEST_NAMES
+       char alpha[] = "0123456789abcdef";
+       int num = rand();
+#endif
+
+       cl_assert(_clar_sandbox[0] == '\0');
+
+       /*
+        * We may want to use test names as sandbox directory names for
+        * readability, _however_ on platforms with restrictions for short
+        * file / folder names (eg, Windows), this may be too long.
+        */
+#ifdef CLAR_SANDBOX_TEST_NAMES
+       cl_assert(strlen(_clar_tempdir) + strlen(suite_name) + strlen(test_name) + 3 < CLAR_MAX_PATH);
+
+       strcpy(_clar_sandbox, _clar_tempdir);
+       _clar_sandbox[_clar_tempdir_len] = '/';
+       _clar_sandbox[_clar_tempdir_len + 1] = '\0';
+
+       append(_clar_sandbox, suite_name);
+       append(_clar_sandbox, "__");
+       append(_clar_sandbox, test_name);
+#else
+       ((void)suite_name);
+       ((void)test_name);
+       ((void)append);
+
+       cl_assert(strlen(_clar_tempdir) + 9 < CLAR_MAX_PATH);
+
+       strcpy(_clar_sandbox, _clar_tempdir);
+       _clar_sandbox[_clar_tempdir_len] = '/';
+
+       _clar_sandbox[_clar_tempdir_len + 1] = alpha[(num & 0xf0000000) >> 28];
+       _clar_sandbox[_clar_tempdir_len + 2] = alpha[(num & 0x0f000000) >> 24];
+       _clar_sandbox[_clar_tempdir_len + 3] = alpha[(num & 0x00f00000) >> 20];
+       _clar_sandbox[_clar_tempdir_len + 4] = alpha[(num & 0x000f0000) >> 16];
+       _clar_sandbox[_clar_tempdir_len + 5] = alpha[(num & 0x0000f000) >> 12];
+       _clar_sandbox[_clar_tempdir_len + 6] = alpha[(num & 0x00000f00) >> 8];
+       _clar_sandbox[_clar_tempdir_len + 7] = alpha[(num & 0x000000f0) >> 4];
+       _clar_sandbox[_clar_tempdir_len + 8] = alpha[(num & 0x0000000f) >> 0];
+       _clar_sandbox[_clar_tempdir_len + 9] = '\0';
+#endif
+
+       if (mkdir(_clar_sandbox, 0700) != 0)
+               return -1;
+
+       if (chdir(_clar_sandbox) != 0)
+               return -1;
+
+       return 0;
+}
+
+static int clar_sandbox_cleanup(void)
+{
+       cl_assert(_clar_sandbox[0] != '\0');
+
+       if (chdir(_clar_tempdir) != 0)
+               return -1;
+
+       fs_rm(_clar_sandbox);
+       _clar_sandbox[0] = '\0';
+
+       return 0;
+}
+
+const char *clar_tempdir_path(void)
+{
+       return _clar_tempdir;
 }
 
 const char *clar_sandbox_path(void)
 {
-       return _clar_path;
+       return _clar_sandbox;
 }
index 0d0b646fe7514bfece0a7a40c5280d7ad973474d..7b85f162d8ed41b9fbf142aff70f2156576fa996 100644 (file)
@@ -23,10 +23,11 @@ static int clar_summary_testsuite(struct clar_summary *summary,
     int idn, const char *name, time_t timestamp,
     int test_count, int fail_count, int error_count)
 {
-       struct tm *tm = localtime(&timestamp);
+       struct tm tm;
        char iso_dt[20];
 
-       if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+       localtime_r(&timestamp, &tm);
+       if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", &tm) == 0)
                return -1;
 
        return fprintf(summary->fp, "\t<testsuite"
diff --git a/t/unit-tests/clar/example/CMakeLists.txt b/t/unit-tests/clar/example/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b72f187
--- /dev/null
@@ -0,0 +1,28 @@
+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 example.c
+       WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+)
+
+add_executable(example)
+set_target_properties(example PROPERTIES
+       C_STANDARD 90
+       C_STANDARD_REQUIRED ON
+       C_EXTENSIONS OFF
+)
+target_sources(example PRIVATE
+       main.c
+       example.c
+       "${CMAKE_CURRENT_BINARY_DIR}/clar.suite"
+)
+target_compile_definitions(example PRIVATE)
+target_compile_options(example PRIVATE
+       $<IF:$<CXX_COMPILER_ID:MSVC>,/W4,-Wall>
+)
+target_include_directories(example PRIVATE
+       "${CMAKE_SOURCE_DIR}"
+       "${CMAKE_CURRENT_BINARY_DIR}"
+)
+target_link_libraries(example clar)
diff --git a/t/unit-tests/clar/example/example.c b/t/unit-tests/clar/example/example.c
new file mode 100644 (file)
index 0000000..c07d6bf
--- /dev/null
@@ -0,0 +1,6 @@
+#include "clar.h"
+
+void test_example__simple_assert(void)
+{
+       cl_assert_equal_i(1, 1);
+}
similarity index 96%
rename from t/unit-tests/clar/test/main.c.sample
rename to t/unit-tests/clar/example/main.c
index a4d91b72fa82a98fb81d6725831fb808c9c7208f..f8def7fa6ed355be65bf4a07c0e3f517a6c5e92e 100644 (file)
@@ -5,7 +5,7 @@
  * For full terms see the included COPYING file.
  */
 
-#include "clar_test.h"
+#include "clar.h"
 
 /*
  * Minimal main() for clar tests.
index 7f2c1dc17a9ac0f54b109a637790e120807f1501..96abd6ed93177f8d6f59dedd108b25035fc11380 100644 (file)
@@ -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)
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
deleted file mode 100644 (file)
index 0fcaa63..0000000
+++ /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
diff --git a/t/unit-tests/clar/test/expected/help b/t/unit-tests/clar/test/expected/help
new file mode 100644 (file)
index 0000000..4b2be69
--- /dev/null
@@ -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)
diff --git a/t/unit-tests/clar/test/expected/quiet b/t/unit-tests/clar/test/expected/quiet
new file mode 100644 (file)
index 0000000..9751641
--- /dev/null
@@ -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
+
diff --git a/t/unit-tests/clar/test/expected/specific_test b/t/unit-tests/clar/test/expected/specific_test
new file mode 100644 (file)
index 0000000..afa2150
--- /dev/null
@@ -0,0 +1,9 @@
+Loaded 1 suites: 
+Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')
+F
+
+  1) Failure:
+selftest::suite::bool [file:42]
+  0 != value
+  0 != 1
+
diff --git a/t/unit-tests/clar/test/expected/stop_on_failure b/t/unit-tests/clar/test/expected/stop_on_failure
new file mode 100644 (file)
index 0000000..1156ade
--- /dev/null
@@ -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
+
diff --git a/t/unit-tests/clar/test/expected/suite_names b/t/unit-tests/clar/test/expected/suite_names
new file mode 100644 (file)
index 0000000..1b0f639
--- /dev/null
@@ -0,0 +1,2 @@
+Test suites (use -s<name> to run just one):
+   0: selftest::suite
diff --git a/t/unit-tests/clar/test/expected/summary.xml b/t/unit-tests/clar/test/expected/summary.xml
new file mode 100644 (file)
index 0000000..9034a03
--- /dev/null
@@ -0,0 +1,45 @@
+<testsuites>
+       <testsuite id="0" name="selftest" hostname="localhost" timestamp="2024-09-06T10:04:08" tests="8" failures="8" errors="0">
+               <testcase name="1" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[Function call failed: -1
+(null)]]></failure>
+               </testcase>
+               <testcase name="2" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[Expression is not true: 100 == 101
+(null)]]></failure>
+               </testcase>
+               <testcase name="strings" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[String mismatch: "mismatched" != actual ("this one fails")
+'mismatched' != 'expected' (at byte 0)]]></failure>
+               </testcase>
+               <testcase name="strings_with_length" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[String mismatch: "exactly" != actual ("this one fails")
+'exa' != 'exp' (at byte 2)]]></failure>
+               </testcase>
+               <testcase name="int" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[101 != value ("extra note on failing test")
+101 != 100]]></failure>
+               </testcase>
+               <testcase name="int_fmt" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[022 != value
+0022 != 0144]]></failure>
+               </testcase>
+               <testcase name="bool" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[0 != value
+0 != 1]]></failure>
+               </testcase>
+               <testcase name="ptr" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[Pointer mismatch: p1 != p2
+0x1 != 0x2]]></failure>
+               </testcase>
+               <testcase name="multiline_description" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[Function call failed: âˆ’1
+description line 1
+description line 2]]></failure>
+               </testcase>
+               <testcase name="null_string" classname="selftest" time="0.00">
+                       <failure type="assert"><![CDATA[String mismatch: "expected" != actual ("this one fails")
+'expected' != NULL]]></failure>
+               </testcase>
+       </testsuite>
+</testsuites>
diff --git a/t/unit-tests/clar/test/expected/summary_with_filename b/t/unit-tests/clar/test/expected/summary_with_filename
new file mode 100644 (file)
index 0000000..a5f4d40
--- /dev/null
@@ -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 different.xml
diff --git a/t/unit-tests/clar/test/expected/summary_without_filename b/t/unit-tests/clar/test/expected/summary_without_filename
new file mode 100644 (file)
index 0000000..5984502
--- /dev/null
@@ -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
diff --git a/t/unit-tests/clar/test/expected/tap b/t/unit-tests/clar/test/expected/tap
new file mode 100644 (file)
index 0000000..3dc4973
--- /dev/null
@@ -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
diff --git a/t/unit-tests/clar/test/expected/without_arguments b/t/unit-tests/clar/test/expected/without_arguments
new file mode 100644 (file)
index 0000000..08b67b8
--- /dev/null
@@ -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
+
index 59e56ad255be9c3d2d88daad696f8e231819530b..b1ba2996f1365914de5bd9cf5566f05ff244bacb 100644 (file)
@@ -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);
 }
diff --git a/t/unit-tests/clar/test/selftest.c b/t/unit-tests/clar/test/selftest.c
new file mode 100644 (file)
index 0000000..abd585f
--- /dev/null
@@ -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));
+}
diff --git a/t/unit-tests/clar/test/selftest.h b/t/unit-tests/clar/test/selftest.h
new file mode 100644 (file)
index 0000000..220a350
--- /dev/null
@@ -0,0 +1,3 @@
+#include "clar.h"
+
+extern const char *selftest_binary_path;
diff --git a/t/unit-tests/clar/test/selftest_suite/CMakeLists.txt b/t/unit-tests/clar/test/selftest_suite/CMakeLists.txt
new file mode 100644 (file)
index 0000000..9597d67
--- /dev/null
@@ -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)
diff --git a/t/unit-tests/clar/test/selftest_suite/main.c b/t/unit-tests/clar/test/selftest_suite/main.c
new file mode 100644 (file)
index 0000000..3ab581d
--- /dev/null
@@ -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);
+}
similarity index 62%
rename from t/unit-tests/clar/test/sample.c
rename to t/unit-tests/clar/test/selftest_suite/selftest_suite.c
index faa1209262f0c9235d7f015238abe33ed598bc3a..77f872128c709dd082d9e15577a5ef841761e0be 100644 (file)
@@ -1,6 +1,7 @@
-#include "clar_test.h"
 #include <sys/stat.h>
 
+#include "clar.h"
+
 static int file_size(const char *filename)
 {
        struct stat st;
@@ -10,19 +11,14 @@ static int file_size(const char *filename)
        return -1;
 }
 
-void test_sample__initialize(void)
-{
-       global_test_counter++;
-}
-
-void test_sample__cleanup(void)
+void test_selftest_suite__cleanup(void)
 {
        cl_fixture_cleanup("test");
 
        cl_assert(file_size("test/file") == -1);
 }
 
-void test_sample__1(void)
+void test_selftest_suite__1(void)
 {
        cl_assert(1);
        cl_must_pass(0);  /* 0 == success */
@@ -30,7 +26,7 @@ void test_sample__1(void)
        cl_must_pass(-1); /* demonstrate a failing call */
 }
 
-void test_sample__2(void)
+void test_selftest_suite__2(void)
 {
        cl_fixture_sandbox("test");
 
@@ -39,7 +35,7 @@ void test_sample__2(void)
        cl_assert(100 == 101);
 }
 
-void test_sample__strings(void)
+void test_selftest_suite__strings(void)
 {
        const char *actual = "expected";
        cl_assert_equal_s("expected", actual);
@@ -47,7 +43,7 @@ void test_sample__strings(void)
        cl_assert_equal_s_("mismatched", actual, "this one fails");
 }
 
-void test_sample__strings_with_length(void)
+void test_selftest_suite__strings_with_length(void)
 {
        const char *actual = "expected";
        cl_assert_equal_strn("expected_", actual, 8);
@@ -56,29 +52,41 @@ void test_sample__strings_with_length(void)
        cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
 }
 
-void test_sample__int(void)
+void test_selftest_suite__int(void)
 {
        int value = 100;
        cl_assert_equal_i(100, value);
        cl_assert_equal_i_(101, value, "extra note on failing test");
 }
 
-void test_sample__int_fmt(void)
+void test_selftest_suite__int_fmt(void)
 {
        int value = 100;
        cl_assert_equal_i_fmt(022, value, "%04o");
 }
 
-void test_sample__bool(void)
+void test_selftest_suite__bool(void)
 {
        int value = 100;
        cl_assert_equal_b(1, value);       /* test equality as booleans */
        cl_assert_equal_b(0, value);
 }
 
-void test_sample__ptr(void)
+void test_selftest_suite__ptr(void)
 {
-       const char *actual = "expected";
-       cl_assert_equal_p(actual, actual); /* pointers to same object */
-       cl_assert_equal_p(&actual, actual);
+       void *p1 = (void *)0x1, *p2 = (void *)0x2;
+       cl_assert_equal_p(p1, p1); /* pointers to same object */
+       cl_assert_equal_p(p1, p2);
+}
+
+void test_selftest_suite__multiline_description(void)
+{
+       cl_must_pass_(-1, "description line 1\ndescription line 2");
+}
+
+void test_selftest_suite__null_string(void)
+{
+       const char *actual = NULL;
+       cl_assert_equal_s(actual, actual);
+       cl_assert_equal_s_("expected", actual, "this one fails");
 }