* [RFC PATCH 1/3] t: import the clar unit testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
@ 2024-07-31 9:04 ` Patrick Steinhardt
2024-07-31 18:27 ` Josh Steadmon
2024-07-31 9:04 ` [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (10 subsequent siblings)
11 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-07-31 9:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon
[-- Attachment #1: Type: text/plain, Size: 86109 bytes --]
Import the clar unit testing framework at commit faa8419 (Merge pull
request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 154 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 267 +++++++
t/unit-tests/clar/test/.gitignore | 5 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
18 files changed, 2941 insertions(+)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 0000000000..b1ac2de460
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 0000000000..8983817f0c
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 0000000000..a8961c5f10
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 0000000000..3fc2c76815
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 0000000000..8c22382bd5
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 0000000000..6ec6423484
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 0000000000..a6eda5e5dc
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 0000000000..c17e2f693b
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 0000000000..0ba1479620
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,154 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
+
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 0000000000..4dd352e28b
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 0000000000..931b4d60d9
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
+
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 0000000000..3e8ae0a3a5
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 0000000000..93c6b2ad32
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 0000000000..0fcaa639aa
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 0000000000..59e56ad255
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 0000000000..a4d91b72fa
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 0000000000..220f4aa98a
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 0000000000..faa1209262
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-07-31 9:04 ` [RFC PATCH 1/3] t: import the clar unit " Patrick Steinhardt
@ 2024-07-31 18:27 ` Josh Steadmon
2024-07-31 19:36 ` Junio C Hamano
2024-07-31 20:04 ` rsbecker
0 siblings, 2 replies; 172+ messages in thread
From: Josh Steadmon @ 2024-07-31 18:27 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood
On 2024.07.31 11:04, Patrick Steinhardt wrote:
> Import the clar unit testing framework at commit faa8419 (Merge pull
> request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
> will be wired up in subsequent commits.
Rather than forking our own copy of clar, could we just add it as a
submodule instead?
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-07-31 18:27 ` Josh Steadmon
@ 2024-07-31 19:36 ` Junio C Hamano
2024-08-01 9:32 ` Patrick Steinhardt
2024-07-31 20:04 ` rsbecker
1 sibling, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-07-31 19:36 UTC (permalink / raw)
To: Josh Steadmon
Cc: Patrick Steinhardt, git, René Scharfe, Kyle Lippincott,
Phillip Wood
Josh Steadmon <steadmon@google.com> writes:
> On 2024.07.31 11:04, Patrick Steinhardt wrote:
>> Import the clar unit testing framework at commit faa8419 (Merge pull
>> request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
>> will be wired up in subsequent commits.
>
> Rather than forking our own copy of clar, could we just add it as a
> submodule instead?
I doubt that forking was Patrick's intention to begin with.
Convenience is.
It certainly is a possibility to bind it as a submodule, but it
would be easier for everybody to futz with and experiment, at least
while it is in its RFC phase.
Thanks.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-07-31 19:36 ` Junio C Hamano
@ 2024-08-01 9:32 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:32 UTC (permalink / raw)
To: Junio C Hamano
Cc: Josh Steadmon, git, René Scharfe, Kyle Lippincott,
Phillip Wood
[-- Attachment #1: Type: text/plain, Size: 981 bytes --]
On Wed, Jul 31, 2024 at 12:36:51PM -0700, Junio C Hamano wrote:
> Josh Steadmon <steadmon@google.com> writes:
>
> > On 2024.07.31 11:04, Patrick Steinhardt wrote:
> >> Import the clar unit testing framework at commit faa8419 (Merge pull
> >> request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
> >> will be wired up in subsequent commits.
> >
> > Rather than forking our own copy of clar, could we just add it as a
> > submodule instead?
>
> I doubt that forking was Patrick's intention to begin with.
> Convenience is.
Indeed. If we decide to adopt it I'd like to carry any required changes
upstream.
> It certainly is a possibility to bind it as a submodule, but it
> would be easier for everybody to futz with and experiment, at least
> while it is in its RFC phase.
I'm not too fond of using submodules, but I'm also not strictly against
using them. I'll do whatever works best for folks here and adapt
accordingly.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-07-31 18:27 ` Josh Steadmon
2024-07-31 19:36 ` Junio C Hamano
@ 2024-07-31 20:04 ` rsbecker
2024-08-01 9:31 ` Patrick Steinhardt
1 sibling, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-07-31 20:04 UTC (permalink / raw)
To: 'Josh Steadmon', 'Patrick Steinhardt'
Cc: git, 'René Scharfe', 'Junio C Hamano',
'Kyle Lippincott', 'Phillip Wood'
On Wednesday, July 31, 2024 2:27 PM, Josh Steadmon wrote:
>On 2024.07.31 11:04, Patrick Steinhardt wrote:
>> Import the clar unit testing framework at commit faa8419 (Merge pull
>> request #93 from clar-test/ethomson/fixtures, 2023-12-14). The
>> framework will be wired up in subsequent commits.
>
>Rather than forking our own copy of clar, could we just add it as a
submodule
>instead?
What are the requirements to build/use this?
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-07-31 20:04 ` rsbecker
@ 2024-08-01 9:31 ` Patrick Steinhardt
2024-08-01 12:15 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:31 UTC (permalink / raw)
To: rsbecker
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
[-- Attachment #1: Type: text/plain, Size: 765 bytes --]
On Wed, Jul 31, 2024 at 04:04:29PM -0400, rsbecker@nexbridge.com wrote:
> On Wednesday, July 31, 2024 2:27 PM, Josh Steadmon wrote:
> >On 2024.07.31 11:04, Patrick Steinhardt wrote:
> >> Import the clar unit testing framework at commit faa8419 (Merge pull
> >> request #93 from clar-test/ethomson/fixtures, 2023-12-14). The
> >> framework will be wired up in subsequent commits.
> >
> >Rather than forking our own copy of clar, could we just add it as a
> submodule
> >instead?
>
> What are the requirements to build/use this?
In its current form, Python is a dependency due to "generate.py". I
mentioned elsewhere though that I'd be happy to port it to our language
of choice.
Other than that it should only require a C89 compiler.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 9:31 ` Patrick Steinhardt
@ 2024-08-01 12:15 ` rsbecker
2024-08-01 12:54 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-01 12:15 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 5:32 AM, Patrick Steinhardt wrote:
>On Wed, Jul 31, 2024 at 04:04:29PM -0400, rsbecker@nexbridge.com wrote:
>> On Wednesday, July 31, 2024 2:27 PM, Josh Steadmon wrote:
>> >On 2024.07.31 11:04, Patrick Steinhardt wrote:
>> >> Import the clar unit testing framework at commit faa8419 (Merge
>> >> pull request #93 from clar-test/ethomson/fixtures, 2023-12-14). The
>> >> framework will be wired up in subsequent commits.
>> >
>> >Rather than forking our own copy of clar, could we just add it as a
>> submodule
>> >instead?
>>
>> What are the requirements to build/use this?
>
>In its current form, Python is a dependency due to "generate.py". I
mentioned
>elsewhere though that I'd be happy to port it to our language of choice.
>
>Other than that it should only require a C89 compiler.
Unfortunately, it needs gcc, but that is just because of its Makefile using
cc -Wall (not portable).
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 12:15 ` rsbecker
@ 2024-08-01 12:54 ` rsbecker
2024-08-01 13:37 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-01 12:54 UTC (permalink / raw)
To: rsbecker, 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 8:15 AM, I wrote:
>On Thursday, August 1, 2024 5:32 AM, Patrick Steinhardt wrote:
>>On Wed, Jul 31, 2024 at 04:04:29PM -0400, rsbecker@nexbridge.com wrote:
>>> On Wednesday, July 31, 2024 2:27 PM, Josh Steadmon wrote:
>>> >On 2024.07.31 11:04, Patrick Steinhardt wrote:
>>> >> Import the clar unit testing framework at commit faa8419 (Merge
>>> >> pull request #93 from clar-test/ethomson/fixtures, 2023-12-14).
>>> >> The framework will be wired up in subsequent commits.
>>> >
>>> >Rather than forking our own copy of clar, could we just add it as a
>>> submodule
>>> >instead?
>>>
>>> What are the requirements to build/use this?
>>
>>In its current form, Python is a dependency due to "generate.py". I
>mentioned
>>elsewhere though that I'd be happy to port it to our language of choice.
>>
>>Other than that it should only require a C89 compiler.
>
>Unfortunately, it needs gcc, but that is just because of its Makefile using
cc -Wall
>(not portable).
It looks like clar needs CC=c99 when running generate.py, which is fine. But
there is no current way to
suppress -Wall without hand-modifying clar/test/Makefile. The options like
that should either be in
CFLAGS?=... instead of CFLAGS=... or we could introduce CFLAGS_DBG?= and put
-Wall in there - it is
only for reporting warnings which is done different in the c99 I have.
Ideally, the CFLAGS should come
from ./git/config.mak.uname/
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 12:54 ` rsbecker
@ 2024-08-01 13:37 ` Patrick Steinhardt
2024-08-01 13:47 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 13:37 UTC (permalink / raw)
To: rsbecker
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
[-- Attachment #1: Type: text/plain, Size: 1765 bytes --]
On Thu, Aug 01, 2024 at 08:54:36AM -0400, rsbecker@nexbridge.com wrote:
> On Thursday, August 1, 2024 8:15 AM, I wrote:
> >On Thursday, August 1, 2024 5:32 AM, Patrick Steinhardt wrote:
> >>On Wed, Jul 31, 2024 at 04:04:29PM -0400, rsbecker@nexbridge.com wrote:
> >>> On Wednesday, July 31, 2024 2:27 PM, Josh Steadmon wrote:
> >>> >On 2024.07.31 11:04, Patrick Steinhardt wrote:
> >>> >> Import the clar unit testing framework at commit faa8419 (Merge
> >>> >> pull request #93 from clar-test/ethomson/fixtures, 2023-12-14).
> >>> >> The framework will be wired up in subsequent commits.
> >>> >
> >>> >Rather than forking our own copy of clar, could we just add it as a
> >>> submodule
> >>> >instead?
> >>>
> >>> What are the requirements to build/use this?
> >>
> >>In its current form, Python is a dependency due to "generate.py". I
> >mentioned
> >>elsewhere though that I'd be happy to port it to our language of choice.
> >>
> >>Other than that it should only require a C89 compiler.
> >
> >Unfortunately, it needs gcc, but that is just because of its Makefile using
> cc -Wall
> >(not portable).
>
> It looks like clar needs CC=c99 when running generate.py, which is fine. But
> there is no current way to
> suppress -Wall without hand-modifying clar/test/Makefile. The options like
> that should either be in
> CFLAGS?=... instead of CFLAGS=... or we could introduce CFLAGS_DBG?= and put
> -Wall in there - it is
> only for reporting warnings which is done different in the c99 I have.
> Ideally, the CFLAGS should come
> from ./git/config.mak.uname/
As mentioned in another mail, we do not use its Makefile at all. Did you
check whether the version I have proposed here works when running `make
test`?
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 13:37 ` Patrick Steinhardt
@ 2024-08-01 13:47 ` rsbecker
2024-08-01 13:50 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-01 13:47 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
>On Thu, Aug 01, 2024 at 08:54:36AM -0400, rsbecker@nexbridge.com wrote:
>> On Thursday, August 1, 2024 8:15 AM, I wrote:
>> >On Thursday, August 1, 2024 5:32 AM, Patrick Steinhardt wrote:
>> >>On Wed, Jul 31, 2024 at 04:04:29PM -0400, rsbecker@nexbridge.com wrote:
>> >>> On Wednesday, July 31, 2024 2:27 PM, Josh Steadmon wrote:
>> >>> >On 2024.07.31 11:04, Patrick Steinhardt wrote:
>> >>> >> Import the clar unit testing framework at commit faa8419 (Merge
>> >>> >> pull request #93 from clar-test/ethomson/fixtures, 2023-12-14).
>> >>> >> The framework will be wired up in subsequent commits.
>> >>> >
>> >>> >Rather than forking our own copy of clar, could we just add it as
>> >>> >a
>> >>> submodule
>> >>> >instead?
>> >>>
>> >>> What are the requirements to build/use this?
>> >>
>> >>In its current form, Python is a dependency due to "generate.py". I
>> >mentioned
>> >>elsewhere though that I'd be happy to port it to our language of
choice.
>> >>
>> >>Other than that it should only require a C89 compiler.
>> >
>> >Unfortunately, it needs gcc, but that is just because of its Makefile
>> >using
>> cc -Wall
>> >(not portable).
>>
>> It looks like clar needs CC=c99 when running generate.py, which is
>> fine. But there is no current way to suppress -Wall without
>> hand-modifying clar/test/Makefile. The options like that should either
>> be in CFLAGS?=... instead of CFLAGS=... or we could introduce
>> CFLAGS_DBG?= and put -Wall in there - it is only for reporting
>> warnings which is done different in the c99 I have.
>> Ideally, the CFLAGS should come
>> from ./git/config.mak.uname/
>
>As mentioned in another mail, we do not use its Makefile at all. Did you
check
>whether the version I have proposed here works when running `make test`?
That is the commit I have been trying to use. make test in clar or git?
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 13:47 ` rsbecker
@ 2024-08-01 13:50 ` Patrick Steinhardt
2024-08-01 13:53 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 13:50 UTC (permalink / raw)
To: rsbecker
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
[-- Attachment #1: Type: text/plain, Size: 713 bytes --]
On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com wrote:
> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
> >As mentioned in another mail, we do not use its Makefile at all. Did you
> check
> >whether the version I have proposed here works when running `make test`?
>
> That is the commit I have been trying to use. make test in clar or git?
In Git itself. `make test` builds and runs our unit tests, and that now
also includes unit tests based on clar with this patch series. The clar
Makefile exists only because I did a 1:1 import of the upstream
dependency. We could just as well remove it altogether, including other
bits that we don't end up using.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 13:50 ` Patrick Steinhardt
@ 2024-08-01 13:53 ` rsbecker
2024-08-01 13:55 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-01 13:53 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
>On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com wrote:
>> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
>> >As mentioned in another mail, we do not use its Makefile at all. Did
>> >you
>> check
>> >whether the version I have proposed here works when running `make test`?
>>
>> That is the commit I have been trying to use. make test in clar or git?
>
>In Git itself. `make test` builds and runs our unit tests, and that now
also includes
>unit tests based on clar with this patch series. The clar Makefile exists
only because I
>did a 1:1 import of the upstream dependency. We could just as well remove
it
>altogether, including other bits that we don't end up using.
I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and 'next' is
going now. Would that catch it?
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 13:53 ` rsbecker
@ 2024-08-01 13:55 ` Patrick Steinhardt
2024-08-01 14:04 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 13:55 UTC (permalink / raw)
To: rsbecker
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
[-- Attachment #1: Type: text/plain, Size: 1195 bytes --]
On Thu, Aug 01, 2024 at 09:53:52AM -0400, rsbecker@nexbridge.com wrote:
> On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
> >On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com wrote:
> >> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
> >> >As mentioned in another mail, we do not use its Makefile at all. Did
> >> >you
> >> check
> >> >whether the version I have proposed here works when running `make test`?
> >>
> >> That is the commit I have been trying to use. make test in clar or git?
> >
> >In Git itself. `make test` builds and runs our unit tests, and that now
> also includes
> >unit tests based on clar with this patch series. The clar Makefile exists
> only because I
> >did a 1:1 import of the upstream dependency. We could just as well remove
> it
> >altogether, including other bits that we don't end up using.
>
> I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and 'next' is
> going now. Would that catch it?
No, as the patches in this thread are only up for discussion right now
and have not been merged to any of the branches. You'd have to apply
them on top of v2.46.0 first :)
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 13:55 ` Patrick Steinhardt
@ 2024-08-01 14:04 ` rsbecker
2024-08-01 14:43 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-01 14:04 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 9:55 AM, Patrick Steinhardt wrote:
>On Thu, Aug 01, 2024 at 09:53:52AM -0400, rsbecker@nexbridge.com wrote:
>> On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
>> >On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com wrote:
>> >> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
>> >> >As mentioned in another mail, we do not use its Makefile at all.
>> >> >Did you
>> >> check
>> >> >whether the version I have proposed here works when running `make
test`?
>> >>
>> >> That is the commit I have been trying to use. make test in clar or
git?
>> >
>> >In Git itself. `make test` builds and runs our unit tests, and that
>> >now
>> also includes
>> >unit tests based on clar with this patch series. The clar Makefile
>> >exists
>> only because I
>> >did a 1:1 import of the upstream dependency. We could just as well
>> >remove
>> it
>> >altogether, including other bits that we don't end up using.
>>
>> I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and
>> 'next' is going now. Would that catch it?
>
>No, as the patches in this thread are only up for discussion right now and
have not
>been merged to any of the branches. You'd have to apply them on top of
v2.46.0
>first :)
Do you happen to have a public fork?
Regards,
Randall
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 14:04 ` rsbecker
@ 2024-08-01 14:43 ` Patrick Steinhardt
2024-08-01 16:31 ` rsbecker
` (2 more replies)
0 siblings, 3 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 14:43 UTC (permalink / raw)
To: rsbecker
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
[-- Attachment #1: Type: text/plain, Size: 1573 bytes --]
On Thu, Aug 01, 2024 at 10:04:37AM -0400, rsbecker@nexbridge.com wrote:
> On Thursday, August 1, 2024 9:55 AM, Patrick Steinhardt wrote:
> >On Thu, Aug 01, 2024 at 09:53:52AM -0400, rsbecker@nexbridge.com wrote:
> >> On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
> >> >On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com wrote:
> >> >> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
> >> >> >As mentioned in another mail, we do not use its Makefile at all.
> >> >> >Did you
> >> >> check
> >> >> >whether the version I have proposed here works when running `make
> test`?
> >> >>
> >> >> That is the commit I have been trying to use. make test in clar or
> git?
> >> >
> >> >In Git itself. `make test` builds and runs our unit tests, and that
> >> >now
> >> also includes
> >> >unit tests based on clar with this patch series. The clar Makefile
> >> >exists
> >> only because I
> >> >did a 1:1 import of the upstream dependency. We could just as well
> >> >remove
> >> it
> >> >altogether, including other bits that we don't end up using.
> >>
> >> I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and
> >> 'next' is going now. Would that catch it?
> >
> >No, as the patches in this thread are only up for discussion right now and
> have not
> >been merged to any of the branches. You'd have to apply them on top of
> v2.46.0
> >first :)
>
> Do you happen to have a public fork?
You can pull the branch pks-clar-unit-tests from
https://gitlab.com/gitlab-org/git.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 14:43 ` Patrick Steinhardt
@ 2024-08-01 16:31 ` rsbecker
2024-08-01 17:06 ` rsbecker
2024-08-01 17:43 ` [RFC PATCH 1/3] t: import the clar unit testing framework (better one) rsbecker
2 siblings, 0 replies; 172+ messages in thread
From: rsbecker @ 2024-08-01 16:31 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 10:44 AM, Patrick Steinhardt wrote:
>On Thu, Aug 01, 2024 at 10:04:37AM -0400, rsbecker@nexbridge.com wrote:
>> On Thursday, August 1, 2024 9:55 AM, Patrick Steinhardt wrote:
>> >On Thu, Aug 01, 2024 at 09:53:52AM -0400, rsbecker@nexbridge.com wrote:
>> >> On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
>> >> >On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com
>wrote:
>> >> >> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
>> >> >> >As mentioned in another mail, we do not use its Makefile at all.
>> >> >> >Did you
>> >> >> check
>> >> >> >whether the version I have proposed here works when running
>> >> >> >`make
>> test`?
>> >> >>
>> >> >> That is the commit I have been trying to use. make test in clar
>> >> >> or
>> git?
>> >> >
>> >> >In Git itself. `make test` builds and runs our unit tests, and
>> >> >that now
>> >> also includes
>> >> >unit tests based on clar with this patch series. The clar Makefile
>> >> >exists
>> >> only because I
>> >> >did a 1:1 import of the upstream dependency. We could just as well
>> >> >remove
>> >> it
>> >> >altogether, including other bits that we don't end up using.
>> >>
>> >> I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and
>> >> 'next' is going now. Would that catch it?
>> >
>> >No, as the patches in this thread are only up for discussion right
>> >now and
>> have not
>> >been merged to any of the branches. You'd have to apply them on top
>> >of
>> v2.46.0
>> >first :)
>>
>> Do you happen to have a public fork?
>
>You can pull the branch pks-clar-unit-tests from
https://gitlab.com/gitlab-org/git.
Two issues so far:
CC t/unit-tests/clar/clar.o
assert(!"Unexpected commandline argument!");
^
"/home/randall/git-clar/t/unit-tests/clar/clar.c", line 547: warning(317):
controlling expression is constant
if (mkdtemp(_clar_path) == NULL)
^
"/home/randall/git-clar/t/unit-tests/clar/clar/sandbox.h", line 132:
error(114):
identifier "mkdtemp" is undefined
mkdtemp is not portable. assert was not #included. Thoughts?
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework
2024-08-01 14:43 ` Patrick Steinhardt
2024-08-01 16:31 ` rsbecker
@ 2024-08-01 17:06 ` rsbecker
2024-08-01 17:43 ` [RFC PATCH 1/3] t: import the clar unit testing framework (better one) rsbecker
2 siblings, 0 replies; 172+ messages in thread
From: rsbecker @ 2024-08-01 17:06 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 10:44 AM Patrick Steinhardt wrote:
>On Thu, Aug 01, 2024 at 10:04:37AM -0400, rsbecker@nexbridge.com wrote:
>> On Thursday, August 1, 2024 9:55 AM, Patrick Steinhardt wrote:
>> >On Thu, Aug 01, 2024 at 09:53:52AM -0400, rsbecker@nexbridge.com wrote:
>> >> On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
>> >> >On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com
>wrote:
>> >> >> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
>> >> >> >As mentioned in another mail, we do not use its Makefile at all.
>> >> >> >Did you
>> >> >> check
>> >> >> >whether the version I have proposed here works when running
>> >> >> >`make
>> test`?
>> >> >>
>> >> >> That is the commit I have been trying to use. make test in clar
>> >> >> or
>> git?
>> >> >
>> >> >In Git itself. `make test` builds and runs our unit tests, and
>> >> >that now
>> >> also includes
>> >> >unit tests based on clar with this patch series. The clar Makefile
>> >> >exists
>> >> only because I
>> >> >did a 1:1 import of the upstream dependency. We could just as well
>> >> >remove
>> >> it
>> >> >altogether, including other bits that we don't end up using.
>> >>
>> >> I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and
>> >> 'next' is going now. Would that catch it?
>> >
>> >No, as the patches in this thread are only up for discussion right
>> >now and
>> have not
>> >been merged to any of the branches. You'd have to apply them on top
>> >of
>> v2.46.0
>> >first :)
>>
>> Do you happen to have a public fork?
>
>You can pull the branch pks-clar-unit-tests from
https://gitlab.com/gitlab-org/git.
Fix #1:
diff --git a/t/unit-tests/clar/clar/sandbox.h
b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..81a40f26b8 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -116,7 +116,7 @@ static int build_sandbox_path(void)
strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
-#if defined(__MINGW32__)
+#if defined(__MINGW32__) || defined(__TANDEM)
if (_mktemp(_clar_path) == NULL)
return -1;
That gets past the clar build problem. Probably more to come as I encounter
issues. The assert issue was just a warning about a constant expression that
will always fail.
^ permalink raw reply related [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework (better one)
2024-08-01 14:43 ` Patrick Steinhardt
2024-08-01 16:31 ` rsbecker
2024-08-01 17:06 ` rsbecker
@ 2024-08-01 17:43 ` rsbecker
2024-08-01 18:12 ` René Scharfe
2 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-01 17:43 UTC (permalink / raw)
To: 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'René Scharfe',
'Junio C Hamano', 'Kyle Lippincott',
'Phillip Wood'
On Thursday, August 1, 2024 10:44 AM, Patrick Steinhardt wrote:
>On Thu, Aug 01, 2024 at 10:04:37AM -0400, rsbecker@nexbridge.com wrote:
>> On Thursday, August 1, 2024 9:55 AM, Patrick Steinhardt wrote:
>> >On Thu, Aug 01, 2024 at 09:53:52AM -0400, rsbecker@nexbridge.com wrote:
>> >> On Thursday, August 1, 2024 9:50 AM, Patrick Steinhardt wrote:
>> >> >On Thu, Aug 01, 2024 at 09:47:38AM -0400, rsbecker@nexbridge.com
>wrote:
>> >> >> On Thursday, August 1, 2024 9:37 AM, Patrick Steinhardt wrote:
>> >> >> >As mentioned in another mail, we do not use its Makefile at all.
>> >> >> >Did you
>> >> >> check
>> >> >> >whether the version I have proposed here works when running
>> >> >> >`make
>> test`?
>> >> >>
>> >> >> That is the commit I have been trying to use. make test in clar
>> >> >> or
>> git?
>> >> >
>> >> >In Git itself. `make test` builds and runs our unit tests, and
>> >> >that now
>> >> also includes
>> >> >unit tests based on clar with this patch series. The clar Makefile
>> >> >exists
>> >> only because I
>> >> >did a 1:1 import of the upstream dependency. We could just as well
>> >> >remove
>> >> it
>> >> >altogether, including other bits that we don't end up using.
>> >>
>> >> I see. Well, the 2.46.0 test passes. I ran 'seen' 5 days ago and
>> >> 'next' is going now. Would that catch it?
>> >
>> >No, as the patches in this thread are only up for discussion right
>> >now and
>> have not
>> >been merged to any of the branches. You'd have to apply them on top
>> >of
>> v2.46.0
>> >first :)
>>
>> Do you happen to have a public fork?
>
>You can pull the branch pks-clar-unit-tests from
https://gitlab.com/gitlab-org/git.
This fix is better (and actually works unlike the previous):
diff --git a/t/unit-tests/clar/clar/sandbox.h
b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..1b14dbfd76 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
This is based on the above repo/branch. Basically a separate block for
__TANDEM for temp directories. Build is done. I'm running the full test now
and may report more or success.
Regards,
Randall
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 1/3] t: import the clar unit testing framework (better one)
2024-08-01 17:43 ` [RFC PATCH 1/3] t: import the clar unit testing framework (better one) rsbecker
@ 2024-08-01 18:12 ` René Scharfe
2024-08-01 18:33 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: René Scharfe @ 2024-08-01 18:12 UTC (permalink / raw)
To: rsbecker, 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'Junio C Hamano',
'Kyle Lippincott', 'Phillip Wood'
Am 01.08.24 um 19:43 schrieb rsbecker@nexbridge.com:
>
> I'm running the full test now and may report more or success.
"make unit-tests" would suffice.
René
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 1/3] t: import the clar unit testing framework (better one)
2024-08-01 18:12 ` René Scharfe
@ 2024-08-01 18:33 ` rsbecker
0 siblings, 0 replies; 172+ messages in thread
From: rsbecker @ 2024-08-01 18:33 UTC (permalink / raw)
To: 'René Scharfe', 'Patrick Steinhardt'
Cc: 'Josh Steadmon', git, 'Junio C Hamano',
'Kyle Lippincott', 'Phillip Wood'
On Thursday, August 1, 2024 2:13 PM, René Scharfe wrote:
>Am 01.08.24 um 19:43 schrieb rsbecker@nexbridge.com:
>>
>> I'm running the full test now and may report more or success.
>
>"make unit-tests" would suffice.
Well, that takes a whole lot less time, thanks. 100% pass after the prior fix was applied.
Thank you.
Randall
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
2024-07-31 9:04 ` [RFC PATCH 1/3] t: import the clar unit " Patrick Steinhardt
@ 2024-07-31 9:04 ` Patrick Steinhardt
2024-07-31 16:48 ` René Scharfe
2024-07-31 17:01 ` Junio C Hamano
2024-07-31 9:04 ` [RFC PATCH 3/3] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (9 subsequent siblings)
11 siblings, 2 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-07-31 9:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon
[-- Attachment #1: Type: text/plain, Size: 7299 bytes --]
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 34 ++++++++++++++++++++++++++++++----
t/Makefile | 1 +
t/unit-tests/.gitignore | 3 +++
t/unit-tests/unit-test.c | 16 ++++++++++++++++
t/unit-tests/unit-test.h | 3 +++
6 files changed, 54 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c2..6687bd6db4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index d6479092a0..658acb4d48 100644
--- a/Makefile
+++ b/Makefile
@@ -1332,6 +1332,11 @@ THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2711,6 +2716,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3213,7 +3219,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3644,7 +3650,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3700,6 +3706,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/.clarcache
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3859,7 +3866,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ rm -f $(UNIT_TESTS_DIR)/.clarcache; \
+ fi
+
+$(UNIT_TEST_DIR)/clar.suite: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)$(UNIT_TEST_DIR)/clar/generate.py $(UNIT_TEST_DIR) >/dev/null
+ @touch $@
+$(UNIT_TEST_DIR)/clar-decls.h: $(UNIT_TEST_DIR)/clar.suite
+ $(QUIET_GEN)grep '^extern void' $^ >$@
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) -I$(UNIT_TEST_DIR)/clar
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06f..55f740ae29 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec..b8d46f7bb1 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,4 @@
/bin
+/.clarcache
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 0000000000..d7eecc384c
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,16 @@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **args;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+ DUP_ARRAY(args, argv, argc + 1);
+ args[argc++] = "-t";
+
+ ret = clar_test(argc, (char **) args);
+
+ free(args);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 0000000000..99d59df1b0
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar.h"
+#include "clar-decls.h"
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework
2024-07-31 9:04 ` [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-07-31 16:48 ` René Scharfe
2024-08-01 9:32 ` Patrick Steinhardt
2024-07-31 17:01 ` Junio C Hamano
1 sibling, 1 reply; 172+ messages in thread
From: René Scharfe @ 2024-07-31 16:48 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: Junio C Hamano, Kyle Lippincott, Phillip Wood, Josh Steadmon
Am 31.07.24 um 11:04 schrieb Patrick Steinhardt:
> Wire up the clar unit testing framework by introducing a new
> "unit-tests" executable. In contrast to the existing framework, this
> will result in a single executable for all test suites. The ability to
> pick specific tests to execute is retained via functionality built into
> the clar itself.
>
> Note that we need to be a bit careful about how we need to invalidate
> our Makefile rules. While we obviously have to regenerate the clar suite
> when our test suites change, we also have to invalidate it in case any
> of the test suites gets removed. We do so by using our typical pattern
> of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
> of test suites changes, so that we can easily depend on that file.
>
> Another specialty is that we generate a "clar-decls.h" file. The test
> functions are neither static, nor do they have external declarations.
> This is because they are getting parsed via "generate.py", which then
> creates the external generations that get populated into an array. These
> declarations are only seen by the main function though.
>
> The consequence is that we will get a bunch of "missing prototypes"
> errors from our compiler for each of these test functions. To fix those
> errors, we extract the `extern` declarations from "clar.suite" and put
> them into a standalone header that then gets included by each of our
> unit tests. This gets rid of compiler warnings for every function which
> has been extracted by "generate.py". More importantly though, it does
> _not_ get rid of warnings in case a function really isn't being used by
> anything. Thus, it would cause a compiler error if a function name was
> mistyped and thus not picked up by "generate.py".
>
> +$(UNIT_TEST_DIR)/clar.suite: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
> + $(QUIET_GEN)$(UNIT_TEST_DIR)/clar/generate.py $(UNIT_TEST_DIR) >/dev/null
This uses the Python interpreter from the shebang line in generate.py,
which is python. On my system I only have python3 and python3.12, but
not python. Easily fixed, of course, but a way to configure the
interpreter name would be nice.
This gave me extra motivation to come up with the clunky patch below
to replace Python with sed and awk. That and your statement that clar
doesn't have to be perfect in the other thread. ;)
It reverses the order of dependencies (builds clar-decls.h first), not
sure if that has downsides. And the sed pattern is simpler than the
one in generate.py, just out of laziness.
René
---
.gitignore | 1 -
Makefile | 20 ++++++-----------
t/unit-tests/.gitignore | 1 -
t/unit-tests/generate.awk | 47 +++++++++++++++++++++++++++++++++++++++
4 files changed, 54 insertions(+), 15 deletions(-)
create mode 100644 t/unit-tests/generate.awk
diff --git a/.gitignore b/.gitignore
index 6687bd6db4..8caf3700c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
-/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 8ebcbdc95a..1ffde38de5 100644
--- a/Makefile
+++ b/Makefile
@@ -3706,7 +3706,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
- $(RM) $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/.clarcache
+ $(RM) $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3866,19 +3866,13 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
-GIT-TEST-SUITES: FORCE
- @FLAGS='$(UNIT_TESTS_SUITES)'; \
- if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
- echo >&2 " * new test suites"; \
- echo "$$FLAGS" >GIT-TEST-SUITES; \
- rm -f $(UNIT_TESTS_DIR)/.clarcache; \
- fi
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h $(UNIT_TEST_DIR)/generate.awk
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/generate.awk $(UNIT_TEST_DIR)/clar-decls.h >$@
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES))
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
-$(UNIT_TEST_DIR)/clar.suite: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
- $(QUIET_GEN)$(UNIT_TEST_DIR)/clar/generate.py $(UNIT_TEST_DIR) >/dev/null
- @touch $@
-$(UNIT_TEST_DIR)/clar-decls.h: $(UNIT_TEST_DIR)/clar.suite
- $(QUIET_GEN)grep '^extern void' $^ >$@
$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) -I$(UNIT_TEST_DIR)/clar
$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index b8d46f7bb1..d0632ec7f9 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1,4 +1,3 @@
/bin
-/.clarcache
/clar.suite
/clar-decls.h
diff --git a/t/unit-tests/generate.awk b/t/unit-tests/generate.awk
new file mode 100644
index 0000000000..3c78253866
--- /dev/null
+++ b/t/unit-tests/generate.awk
@@ -0,0 +1,47 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
--
2.46.0
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework
2024-07-31 16:48 ` René Scharfe
@ 2024-08-01 9:32 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:32 UTC (permalink / raw)
To: René Scharfe
Cc: git, Junio C Hamano, Kyle Lippincott, Phillip Wood, Josh Steadmon
[-- Attachment #1: Type: text/plain, Size: 3227 bytes --]
On Wed, Jul 31, 2024 at 06:48:36PM +0200, René Scharfe wrote:
> Am 31.07.24 um 11:04 schrieb Patrick Steinhardt:
> > Wire up the clar unit testing framework by introducing a new
> > "unit-tests" executable. In contrast to the existing framework, this
> > will result in a single executable for all test suites. The ability to
> > pick specific tests to execute is retained via functionality built into
> > the clar itself.
> >
> > Note that we need to be a bit careful about how we need to invalidate
> > our Makefile rules. While we obviously have to regenerate the clar suite
> > when our test suites change, we also have to invalidate it in case any
> > of the test suites gets removed. We do so by using our typical pattern
> > of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
> > of test suites changes, so that we can easily depend on that file.
> >
> > Another specialty is that we generate a "clar-decls.h" file. The test
> > functions are neither static, nor do they have external declarations.
> > This is because they are getting parsed via "generate.py", which then
> > creates the external generations that get populated into an array. These
> > declarations are only seen by the main function though.
> >
> > The consequence is that we will get a bunch of "missing prototypes"
> > errors from our compiler for each of these test functions. To fix those
> > errors, we extract the `extern` declarations from "clar.suite" and put
> > them into a standalone header that then gets included by each of our
> > unit tests. This gets rid of compiler warnings for every function which
> > has been extracted by "generate.py". More importantly though, it does
> > _not_ get rid of warnings in case a function really isn't being used by
> > anything. Thus, it would cause a compiler error if a function name was
> > mistyped and thus not picked up by "generate.py".
> >
>
> > +$(UNIT_TEST_DIR)/clar.suite: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
> > + $(QUIET_GEN)$(UNIT_TEST_DIR)/clar/generate.py $(UNIT_TEST_DIR) >/dev/null
>
> This uses the Python interpreter from the shebang line in generate.py,
> which is python. On my system I only have python3 and python3.12, but
> not python. Easily fixed, of course, but a way to configure the
> interpreter name would be nice.
>
> This gave me extra motivation to come up with the clunky patch below
> to replace Python with sed and awk. That and your statement that clar
> doesn't have to be perfect in the other thread. ;)
Neat! I would certainly prefer to not have a dependency on Python, and I
think awk(1) is a good alternative here that we already require anyway.
Also, another benefit of having our own script is that it allows us to
be more flexible with how exactly our tests are structured.
> It reverses the order of dependencies (builds clar-decls.h first), not
> sure if that has downsides. And the sed pattern is simpler than the
> one in generate.py, just out of laziness.
We could even integrate the generation of clar-decls.h with the AWK
script.
I'll play around a bit with what you have, thanks a lot for working on
it!
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework
2024-07-31 9:04 ` [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework Patrick Steinhardt
2024-07-31 16:48 ` René Scharfe
@ 2024-07-31 17:01 ` Junio C Hamano
2024-07-31 21:39 ` Junio C Hamano
1 sibling, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-07-31 17:01 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon
A trivial fix-up to be squashed into this step.
Makefile | 1 +
1 file changed, 1 insertion(+)
diff --git c/Makefile w/Makefile
index 8ebcbdc95a..d561789582 100644
--- c/Makefile
+++ w/Makefile
@@ -3735,6 +3735,7 @@ ifndef NO_TCLTK
$(MAKE) -C git-gui clean
endif
$(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-BUILD-OPTIONS
+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar-decls.h
$(RM) GIT-USER-AGENT GIT-PREFIX
$(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS
ifdef MSVC
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework
2024-07-31 17:01 ` Junio C Hamano
@ 2024-07-31 21:39 ` Junio C Hamano
2024-08-01 9:32 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-07-31 21:39 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon
Junio C Hamano <gitster@pobox.com> writes:
> A trivial fix-up to be squashed into this step.
I won't be able to address this today, but "make sparse" and other
auxiliary targets also seem to break, due to their lack of
dependence on the generated clar.suite file.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework
2024-07-31 21:39 ` Junio C Hamano
@ 2024-08-01 9:32 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:32 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon
[-- Attachment #1: Type: text/plain, Size: 772 bytes --]
On Wed, Jul 31, 2024 at 02:39:13PM -0700, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
> > A trivial fix-up to be squashed into this step.
>
> I won't be able to address this today, but "make sparse" and other
> auxiliary targets also seem to break, due to their lack of
> dependence on the generated clar.suite file.
Yeah, as said in the cover letter, this was in quite an unpolished
state, so it is not surprising at all that things break. I just wanted
to provide an unfinished PoC to demonstrate how things would roughly
look like so that we have something to discuss. Definitely not material
for `seen` yet.
Given that initial feedback didn't seem to be all that negative I'm
happy to polish things a bit more.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH 3/3] t/unit-tests: convert strvec tests to use clar
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
2024-07-31 9:04 ` [RFC PATCH 1/3] t: import the clar unit " Patrick Steinhardt
2024-07-31 9:04 ` [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-07-31 9:04 ` Patrick Steinhardt
2024-07-31 15:51 ` [RFC PATCH 0/3] Introduce clar testing framework Junio C Hamano
` (8 subsequent siblings)
11 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-07-31 9:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon
[-- Attachment #1: Type: text/plain, Size: 12147 bytes --]
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-strvec.c => strvec.c} | 124 ++++++++++----------------
2 files changed, 46 insertions(+), 80 deletions(-)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
diff --git a/Makefile b/Makefile
index 658acb4d48..8ebcbdc95a 100644
--- a/Makefile
+++ b/Makefile
@@ -1332,6 +1332,7 @@ THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1348,7 +1349,6 @@ UNIT_TEST_PROGRAMS += t-reftable-basics
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
similarity index 54%
rename from t/unit-tests/t-strvec.c
rename to t/unit-tests/strvec.c
index d4615ab06d..5edb1ae326 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/strvec.c
@@ -1,11 +1,11 @@
-#include "test-lib.h"
+#include "unit-test.h"
#include "strbuf.h"
#include "strvec.h"
#define check_strvec(vec, ...) \
- check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__)
+ check_strvec_loc(__FILE__, __func__, __LINE__, vec, __VA_ARGS__)
LAST_ARG_MUST_BE_NULL
-static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
+static void check_strvec_loc(const char *file, const char *func, size_t line, struct strvec *vec, ...)
{
va_list ap;
size_t nr = 0;
@@ -16,54 +16,47 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
if (!str)
break;
- if (!check_uint(vec->nr, >, nr) ||
- !check_uint(vec->alloc, >, nr) ||
- !check_str(vec->v[nr], str)) {
- struct strbuf msg = STRBUF_INIT;
- strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
- test_assert(loc, msg.buf, 0);
- strbuf_release(&msg);
- va_end(ap);
- return;
- }
+ clar__assert(vec->nr > nr, file, func, line, "Vector size too small", NULL, 1);
+ clar__assert(vec->alloc > nr, file, func, line, "Vector allocation too small", NULL, 1);
+ cl_assert_equal_s(vec->v[nr], str);
nr++;
}
va_end(ap);
- check_uint(vec->nr, ==, nr);
- check_uint(vec->alloc, >=, nr);
- check_pointer_eq(vec->v[nr], NULL);
+ cl_assert(vec->nr == nr);
+ cl_assert(vec->alloc >= nr);
+ cl_assert_equal_p(vec->v[nr], NULL);
}
-static void t_static_init(void)
+void test_strvec__init(void)
{
struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
}
-static void t_dynamic_init(void)
+void test_strvec__dynamic_init(void)
{
struct strvec vec;
strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
}
-static void t_clear(void)
+void test_strvec__clear(void)
{
struct strvec vec = STRVEC_INIT;
strvec_push(&vec, "foo");
strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
}
-static void t_push(void)
+void test_strvec__push(void)
{
struct strvec vec = STRVEC_INIT;
@@ -76,7 +69,7 @@ static void t_push(void)
strvec_clear(&vec);
}
-static void t_pushf(void)
+void test_strvec__pushft_pushf(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushf(&vec, "foo: %d", 1);
@@ -84,7 +77,7 @@ static void t_pushf(void)
strvec_clear(&vec);
}
-static void t_pushl(void)
+void test_strvec__pushl(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -92,7 +85,7 @@ static void t_pushl(void)
strvec_clear(&vec);
}
-static void t_pushv(void)
+void test_strvec__pushv(void)
{
const char *strings[] = {
"foo", "bar", "baz", NULL,
@@ -105,7 +98,7 @@ static void t_pushv(void)
strvec_clear(&vec);
}
-static void t_replace_at_head(void)
+void test_strvec__replace_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -114,7 +107,7 @@ static void t_replace_at_head(void)
strvec_clear(&vec);
}
-static void t_replace_at_tail(void)
+void test_strvec__replace_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -123,7 +116,7 @@ static void t_replace_at_tail(void)
strvec_clear(&vec);
}
-static void t_replace_in_between(void)
+void test_strvec__replace_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -132,7 +125,7 @@ static void t_replace_in_between(void)
strvec_clear(&vec);
}
-static void t_replace_with_substring(void)
+void test_strvec__replace_with_substring(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", NULL);
@@ -141,7 +134,7 @@ static void t_replace_with_substring(void)
strvec_clear(&vec);
}
-static void t_remove_at_head(void)
+void test_strvec__remove_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -150,7 +143,7 @@ static void t_remove_at_head(void)
strvec_clear(&vec);
}
-static void t_remove_at_tail(void)
+void test_strvec__remove_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -159,7 +152,7 @@ static void t_remove_at_tail(void)
strvec_clear(&vec);
}
-static void t_remove_in_between(void)
+void test_strvec__remove_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -168,7 +161,7 @@ static void t_remove_in_between(void)
strvec_clear(&vec);
}
-static void t_pop_empty_array(void)
+void test_strvec__pop_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pop(&vec);
@@ -176,7 +169,7 @@ static void t_pop_empty_array(void)
strvec_clear(&vec);
}
-static void t_pop_non_empty_array(void)
+void test_strvec__pop_non_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -185,7 +178,7 @@ static void t_pop_non_empty_array(void)
strvec_clear(&vec);
}
-static void t_split_empty_string(void)
+void test_strvec__split_empty_string(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "");
@@ -193,7 +186,7 @@ static void t_split_empty_string(void)
strvec_clear(&vec);
}
-static void t_split_single_item(void)
+void test_strvec__split_single_item(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo");
@@ -201,7 +194,7 @@ static void t_split_single_item(void)
strvec_clear(&vec);
}
-static void t_split_multiple_items(void)
+void test_strvec__split_multiple_items(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo bar baz");
@@ -209,7 +202,7 @@ static void t_split_multiple_items(void)
strvec_clear(&vec);
}
-static void t_split_whitespace_only(void)
+void test_strvec__split_whitespace_only(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, " \t\n");
@@ -217,7 +210,7 @@ static void t_split_whitespace_only(void)
strvec_clear(&vec);
}
-static void t_split_multiple_consecutive_whitespaces(void)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo\n\t bar");
@@ -225,7 +218,7 @@ static void t_split_multiple_consecutive_whitespaces(void)
strvec_clear(&vec);
}
-static void t_detach(void)
+void test_strvec__detach(void)
{
struct strvec vec = STRVEC_INIT;
const char **detached;
@@ -233,40 +226,13 @@ static void t_detach(void)
strvec_push(&vec, "foo");
detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
free((char *) detached[0]);
free(detached);
}
-
-int cmd_main(int argc, const char **argv)
-{
- TEST(t_static_init(), "static initialization");
- TEST(t_dynamic_init(), "dynamic initialization");
- TEST(t_clear(), "clear");
- TEST(t_push(), "push");
- TEST(t_pushf(), "pushf");
- TEST(t_pushl(), "pushl");
- TEST(t_pushv(), "pushv");
- TEST(t_replace_at_head(), "replace at head");
- TEST(t_replace_in_between(), "replace in between");
- TEST(t_replace_at_tail(), "replace at tail");
- TEST(t_replace_with_substring(), "replace with substring");
- TEST(t_remove_at_head(), "remove at head");
- TEST(t_remove_in_between(), "remove in between");
- TEST(t_remove_at_tail(), "remove at tail");
- TEST(t_pop_empty_array(), "pop with empty array");
- TEST(t_pop_non_empty_array(), "pop with non-empty array");
- TEST(t_split_empty_string(), "split empty string");
- TEST(t_split_single_item(), "split single item");
- TEST(t_split_multiple_items(), "split multiple items");
- TEST(t_split_whitespace_only(), "split whitespace only");
- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
- TEST(t_detach(), "detach");
- return test_done();
-}
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (2 preceding siblings ...)
2024-07-31 9:04 ` [RFC PATCH 3/3] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-07-31 15:51 ` Junio C Hamano
2024-07-31 15:56 ` rsbecker
2024-08-01 9:31 ` Patrick Steinhardt
2024-07-31 18:33 ` Josh Steadmon
` (7 subsequent siblings)
11 siblings, 2 replies; 172+ messages in thread
From: Junio C Hamano @ 2024-07-31 15:51 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon
Patrick Steinhardt <ps@pks.im> writes:
> - The clar gives us the ability to pick which tests to run via command
> line parameters, which I personally like more than picking the
> specific binary to run.
One thing I am very unhappy about the current t/unit-tests/ is that
the GIT_SKIP_TESTS mechanism is not effective at all. If we can
wrap clar's test selection syntax inside t/Makefile to work with
GIT_SKIP_TESTS (or its superset equivalent), that would be a great
plus.
> - The clar replaces some test assertions that we already have. They
> feel a bit more mature, but overall there aren't all that many
> assertions available. If we wanted to pick it up, then we'd likely
> have to add some more wrappers.
That is a slight bummer, as importing an externally developed one is
with the hope that we won't have to enhance or maintain it, but
we'll see how much burden it will be.
> - The clar uses longjmp instead of manually having to `return` from
> functions in case there was an assertion failure. This is easier to
> work with in my opinion.
>
> Also, note that I only tested this on my Linux machine. I have no clue
> whether this works as-is on Windows, but I do know that libgit2 tests
> run on Linux, macOS and Windows. So it should work in theory, it's just
> a matter of polishing this series.
>
> I'm happy to hear your thoughts on this, even if it ultimately ends up
> being shot down.
Thanks for getting the ball going. Let's see how fast and far it rolls.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 15:51 ` [RFC PATCH 0/3] Introduce clar testing framework Junio C Hamano
@ 2024-07-31 15:56 ` rsbecker
2024-07-31 16:52 ` Junio C Hamano
2024-08-01 9:31 ` Patrick Steinhardt
1 sibling, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-07-31 15:56 UTC (permalink / raw)
To: 'Junio C Hamano', 'Patrick Steinhardt'
Cc: git, 'René Scharfe', 'Kyle Lippincott',
'Phillip Wood', 'Josh Steadmon'
On Wednesday, July 31, 2024 11:51 AM, Junio C Hamano wrote:
>Patrick Steinhardt <ps@pks.im> writes:
>
>> - The clar gives us the ability to pick which tests to run via command
>> line parameters, which I personally like more than picking the
>> specific binary to run.
>
>One thing I am very unhappy about the current t/unit-tests/ is that the
>GIT_SKIP_TESTS mechanism is not effective at all. If we can wrap clar's test selection
>syntax inside t/Makefile to work with GIT_SKIP_TESTS (or its superset equivalent),
>that would be a great plus.
>
>> - The clar replaces some test assertions that we already have. They
>> feel a bit more mature, but overall there aren't all that many
>> assertions available. If we wanted to pick it up, then we'd likely
>> have to add some more wrappers.
>
>That is a slight bummer, as importing an externally developed one is with the hope
>that we won't have to enhance or maintain it, but we'll see how much burden it will
>be.
>
>> - The clar uses longjmp instead of manually having to `return` from
>> functions in case there was an assertion failure. This is easier to
>> work with in my opinion.
>>
>> Also, note that I only tested this on my Linux machine. I have no clue
>> whether this works as-is on Windows, but I do know that libgit2 tests
>> run on Linux, macOS and Windows. So it should work in theory, it's
>> just a matter of polishing this series.
>>
>> I'm happy to hear your thoughts on this, even if it ultimately ends up
>> being shot down.
>
>Thanks for getting the ball going. Let's see how fast and far it rolls.
I'm sorry for being so behind the curve... what is clar and where does it run?
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 15:56 ` rsbecker
@ 2024-07-31 16:52 ` Junio C Hamano
2024-07-31 20:25 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-07-31 16:52 UTC (permalink / raw)
To: rsbecker
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon'
<rsbecker@nexbridge.com> writes:
> I'm sorry for being so behind the curve... what is clar and where does it run?
We have t/unit-test/test-lib.[ch] that are our home-grown unit test
framework. A handful of tests have been written to use it, when you
say "make test", or "(cd t && make)", unit tests binaries linked
with the home-grown unit test framework run.
clar is a _potential_ replacement for our home-grown framework,
suggested here because it would be nicer if we can use off-the-shelf
component instead of having to enhance and maintain our own.
Where and how it runs does not change even after clar turns out to
be good enough for our purpose and we commit to replace our
home-grown unit test framework with it.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 16:52 ` Junio C Hamano
@ 2024-07-31 20:25 ` rsbecker
2024-07-31 20:37 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-07-31 20:25 UTC (permalink / raw)
To: 'Junio C Hamano'
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon'
On Wednesday, July 31, 2024 12:53 PM, Junio C Hamano wrote:
><rsbecker@nexbridge.com> writes:
>
>> I'm sorry for being so behind the curve... what is clar and where does it run?
>
>We have t/unit-test/test-lib.[ch] that are our home-grown unit test framework. A
>handful of tests have been written to use it, when you say "make test", or "(cd t &&
>make)", unit tests binaries linked with the home-grown unit test framework run.
>
>clar is a _potential_ replacement for our home-grown framework, suggested here
>because it would be nicer if we can use off-the-shelf component instead of having
>to enhance and maintain our own.
>
>Where and how it runs does not change even after clar turns out to be good enough
>for our purpose and we commit to replace our home-grown unit test framework
>with it.
Well... I would like to be able to see whether this can be built/used on NonStop just
so I can stay ahead of the curve or be far enough in advance of it to request any
required fixes to make it work on platform.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 20:25 ` rsbecker
@ 2024-07-31 20:37 ` rsbecker
2024-08-01 9:31 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-07-31 20:37 UTC (permalink / raw)
To: rsbecker, 'Junio C Hamano'
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon'
On Wednesday, July 31, 2024 4:25 PM, I wrote:
>On Wednesday, July 31, 2024 12:53 PM, Junio C Hamano wrote:
>><rsbecker@nexbridge.com> writes:
>>
>>> I'm sorry for being so behind the curve... what is clar and where does it run?
>>
>>We have t/unit-test/test-lib.[ch] that are our home-grown unit test
>>framework. A handful of tests have been written to use it, when you
>>say "make test", or "(cd t && make)", unit tests binaries linked with the home-
>grown unit test framework run.
>>
>>clar is a _potential_ replacement for our home-grown framework,
>>suggested here because it would be nicer if we can use off-the-shelf
>>component instead of having to enhance and maintain our own.
>>
>>Where and how it runs does not change even after clar turns out to be
>>good enough for our purpose and we commit to replace our home-grown
>>unit test framework with it.
>
>Well... I would like to be able to see whether this can be built/used on NonStop just
>so I can stay ahead of the curve or be far enough in advance of it to request any
>required fixes to make it work on platform.
After checking out clar, I would say it has potential, but needs to be slightly more
portable to depend on more than gcc CFLAGS (specifically -Wall). I will ask that
of that team. It does look like it might be possible, but it should integrate with the
config.mak.uname settings, so c99 or c11 (or soon c17) could be used with other
CFLAGS from that file so maintainers only have to worry about one location.
--Randall
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 20:37 ` rsbecker
@ 2024-08-01 9:31 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:31 UTC (permalink / raw)
To: rsbecker
Cc: 'Junio C Hamano', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon'
[-- Attachment #1: Type: text/plain, Size: 2046 bytes --]
On Wed, Jul 31, 2024 at 04:37:37PM -0400, rsbecker@nexbridge.com wrote:
> On Wednesday, July 31, 2024 4:25 PM, I wrote:
> >On Wednesday, July 31, 2024 12:53 PM, Junio C Hamano wrote:
> >><rsbecker@nexbridge.com> writes:
> >>
> >>> I'm sorry for being so behind the curve... what is clar and where does it run?
> >>
> >>We have t/unit-test/test-lib.[ch] that are our home-grown unit test
> >>framework. A handful of tests have been written to use it, when you
> >>say "make test", or "(cd t && make)", unit tests binaries linked with the home-
> >grown unit test framework run.
> >>
> >>clar is a _potential_ replacement for our home-grown framework,
> >>suggested here because it would be nicer if we can use off-the-shelf
> >>component instead of having to enhance and maintain our own.
> >>
> >>Where and how it runs does not change even after clar turns out to be
> >>good enough for our purpose and we commit to replace our home-grown
> >>unit test framework with it.
> >
> >Well... I would like to be able to see whether this can be built/used on NonStop just
> >so I can stay ahead of the curve or be far enough in advance of it to request any
> >required fixes to make it work on platform.
>
> After checking out clar, I would say it has potential, but needs to be slightly more
> portable to depend on more than gcc CFLAGS (specifically -Wall). I will ask that
> of that team. It does look like it might be possible, but it should integrate with the
> config.mak.uname settings, so c99 or c11 (or soon c17) could be used with other
> CFLAGS from that file so maintainers only have to worry about one location.
I'm not sure I follow what you're saying. We don't use the Makefile of
clar at all but integrate it into our own build system, so honoring
config.mak.uname should come for free, I think.
In any case, if there are C constructs in clar that do not work right
now, then I'd be happy to address those issues. The clar is overall
quite small, so it shouldn't be all that involved.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 15:51 ` [RFC PATCH 0/3] Introduce clar testing framework Junio C Hamano
2024-07-31 15:56 ` rsbecker
@ 2024-08-01 9:31 ` Patrick Steinhardt
1 sibling, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:31 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon
[-- Attachment #1: Type: text/plain, Size: 1713 bytes --]
On Wed, Jul 31, 2024 at 08:51:00AM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > - The clar gives us the ability to pick which tests to run via command
> > line parameters, which I personally like more than picking the
> > specific binary to run.
>
> One thing I am very unhappy about the current t/unit-tests/ is that
> the GIT_SKIP_TESTS mechanism is not effective at all. If we can
> wrap clar's test selection syntax inside t/Makefile to work with
> GIT_SKIP_TESTS (or its superset equivalent), that would be a great
> plus.
Yeah, I guess that shouldn't be too hard.
> > - The clar replaces some test assertions that we already have. They
> > feel a bit more mature, but overall there aren't all that many
> > assertions available. If we wanted to pick it up, then we'd likely
> > have to add some more wrappers.
>
> That is a slight bummer, as importing an externally developed one is
> with the hope that we won't have to enhance or maintain it, but
> we'll see how much burden it will be.
The clar exposes generic helpers like `cl_assert`, but also comparison
functions like `cl_assert_equal_$t` for pointers, integers and strings.
If anything is missing, it also exposes the building blocks to add
project-specific assertions so that it would be easy to add for example
`cl_assert_equal_oid`.
One thing I have been missing is non-equality comparisons like
`cl_assert_lt_i`/`cl_assert_gt_i`. But adding that to the clar itself
and upstreaming it should be easy enough, also because we know the
people maintaining it.
Other than that I think it's mostly fine and should serve as a good
baseline.
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (3 preceding siblings ...)
2024-07-31 15:51 ` [RFC PATCH 0/3] Introduce clar testing framework Junio C Hamano
@ 2024-07-31 18:33 ` Josh Steadmon
2024-08-01 9:31 ` Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
` (6 subsequent siblings)
11 siblings, 1 reply; 172+ messages in thread
From: Josh Steadmon @ 2024-07-31 18:33 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood
On 2024.07.31 11:04, Patrick Steinhardt wrote:
> Hi,
>
> there's been some discussion around extending our unit testing framework
> to avoid duplication when declaring test functions. Right now, a testing
> function has to be declared and then wired up via the test's main
> function, which can be a bit annoying. In the thread, René proposes an
> alternative that gets rid of this duplication by using macros. And while
> that does solve the issue, there were some concerns about things being
> too much "magic" while at the same time not being flexible enough.
>
> Part of the discussion revolved around whether we maybe want to have a
> proper unit testing framework in our codebase instead of reinventing the
> wheel. As I quite liked the "clar" [2] testing framework from back when
> I was still developing libgit2 I proposed it as a possible alternative.
> This patch series wires up the clar framework as a proof of concept and
> converts the strvec test suite to use it.
>
> The magic to avoid the above code duplication is quite self-contained in
> a "generate.py" file. This script extracts function declarations from
> all unit test suites and then writes those into a "clar.suite" header
> file. All that one needs to do is thus to declare a function with a
> specific name "test_<suite>__<name>" and then everything else gets wired
> up automatically.
>
> Whether this is better than the solution proposed by René is probably a
> matter of taste. While I'm not a huge fan of the macro-based solution,
> I don't want to veto it either (not that I'd have that power anyway). So
> please, you should rather read this as a proof of concept to see how
> alternatives could look like such that we have a better picture of where
> we want to end up.
>
> Some random thoughts:
>
> - The mandated Python dependency is suboptimal in my opinion.
> Rewriting the script in e.g. Perl should be easy enough though, it's
> no rocket science.
>
> - I prefer that the proposed solution results in a single binary as
> compared to one binary per test system.
Does clar allow running test functions in parallel? With multiple
binaries, we can at least run independent tests in parallel (although
right now the unit tests are fewer and so much faster than the shell
tests that it's hardly noticeable).
> - The clar gives us the ability to pick which tests to run via command
> line parameters, which I personally like more than picking the
> specific binary to run.
Yes this is a nice improvement.
> - The clar replaces some test assertions that we already have. They
> feel a bit more mature, but overall there aren't all that many
> assertions available. If we wanted to pick it up, then we'd likely
> have to add some more wrappers.
>
> - The clar uses longjmp instead of manually having to `return` from
> functions in case there was an assertion failure. This is easier to
> work with in my opinion.
>
> Also, note that I only tested this on my Linux machine. I have no clue
> whether this works as-is on Windows, but I do know that libgit2 tests
> run on Linux, macOS and Windows. So it should work in theory, it's just
> a matter of polishing this series.
>
> I'm happy to hear your thoughts on this, even if it ultimately ends up
> being shot down.
As part of the original unit-test series, I wrote a comparison between
different frameworks: Documentation/technical/unit-tests.txt, poorly
rendered at [1]. Could you add a row to the table evaluating clar on the
individual points there?
[1] https://git-scm.com/docs/unit-tests#framework-selection
> Patrick
>
> [1]: <85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de>
> [2]: https://github.com/clar-test/clar
>
> Patrick Steinhardt (3):
> t: import the clar unit testing framework
> Makefile: wire up the clar unit testing framework
> t/unit-tests: convert strvec tests to use clar
>
> .gitignore | 1 +
> Makefile | 36 +-
> t/Makefile | 1 +
> t/unit-tests/.gitignore | 3 +
> t/unit-tests/clar/.github/workflows/ci.yml | 23 +
> t/unit-tests/clar/COPYING | 15 +
> t/unit-tests/clar/README.md | 329 ++++++++
> t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
> t/unit-tests/clar/clar.h | 173 +++++
> t/unit-tests/clar/clar/fixtures.h | 50 ++
> t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
> t/unit-tests/clar/clar/print.h | 211 ++++++
> t/unit-tests/clar/clar/sandbox.h | 154 ++++
> t/unit-tests/clar/clar/summary.h | 143 ++++
> t/unit-tests/clar/generate.py | 267 +++++++
> t/unit-tests/clar/test/.gitignore | 5 +
> t/unit-tests/clar/test/Makefile | 39 +
> t/unit-tests/clar/test/clar_test.h | 16 +
> t/unit-tests/clar/test/main.c | 40 +
> t/unit-tests/clar/test/main.c.sample | 27 +
> t/unit-tests/clar/test/resources/test/file | 1 +
> t/unit-tests/clar/test/sample.c | 84 ++
> t/unit-tests/{t-strvec.c => strvec.c} | 124 ++-
> t/unit-tests/unit-test.c | 16 +
> t/unit-tests/unit-test.h | 3 +
> 25 files changed, 3041 insertions(+), 84 deletions(-)
> create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
> create mode 100644 t/unit-tests/clar/COPYING
> create mode 100644 t/unit-tests/clar/README.md
> create mode 100644 t/unit-tests/clar/clar.c
> create mode 100644 t/unit-tests/clar/clar.h
> create mode 100644 t/unit-tests/clar/clar/fixtures.h
> create mode 100644 t/unit-tests/clar/clar/fs.h
> create mode 100644 t/unit-tests/clar/clar/print.h
> create mode 100644 t/unit-tests/clar/clar/sandbox.h
> create mode 100644 t/unit-tests/clar/clar/summary.h
> create mode 100755 t/unit-tests/clar/generate.py
> create mode 100644 t/unit-tests/clar/test/.gitignore
> create mode 100644 t/unit-tests/clar/test/Makefile
> create mode 100644 t/unit-tests/clar/test/clar_test.h
> create mode 100644 t/unit-tests/clar/test/main.c
> create mode 100644 t/unit-tests/clar/test/main.c.sample
> create mode 100644 t/unit-tests/clar/test/resources/test/file
> create mode 100644 t/unit-tests/clar/test/sample.c
> rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
> create mode 100644 t/unit-tests/unit-test.c
> create mode 100644 t/unit-tests/unit-test.h
>
> --
> 2.46.0.dirty
>
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH 0/3] Introduce clar testing framework
2024-07-31 18:33 ` Josh Steadmon
@ 2024-08-01 9:31 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-01 9:31 UTC (permalink / raw)
To: Josh Steadmon, git, René Scharfe, Junio C Hamano,
Kyle Lippincott, Phillip Wood
[-- Attachment #1: Type: text/plain, Size: 1330 bytes --]
On Wed, Jul 31, 2024 at 11:33:55AM -0700, Josh Steadmon wrote:
> On 2024.07.31 11:04, Patrick Steinhardt wrote:
> > - I prefer that the proposed solution results in a single binary as
> > compared to one binary per test system.
>
> Does clar allow running test functions in parallel? With multiple
> binaries, we can at least run independent tests in parallel (although
> right now the unit tests are fewer and so much faster than the shell
> tests that it's hardly noticeable).
Ah, that's something I didn't think of. clar does not support running
tests in parallel.
As you say, I guess for now that is fine and I'd claim that it likely is
faster to just run all tests sequentially with a single binary if you
also include build times. If that claim isn't true, or if we eventually
grow a huge body of tests, then we should likely revert to having
separate binaries.
> As part of the original unit-test series, I wrote a comparison between
> different frameworks: Documentation/technical/unit-tests.txt, poorly
> rendered at [1]. Could you add a row to the table evaluating clar on the
> individual points there?
>
> [1] https://git-scm.com/docs/unit-tests#framework-selection
Ah, I wasn't aware of this document. I can update it depending on how
the discussion goes overall :)
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH v2 0/7] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (4 preceding siblings ...)
2024-07-31 18:33 ` Josh Steadmon
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (6 more replies)
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
` (5 subsequent siblings)
11 siblings, 7 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 18197 bytes --]
Hi,
this is the second version of my RFC patch series that introduces the
clar testing framework into our unit tests. The intent is to not have to
hand-craft all features of a proper unit testing framework, while still
not painting us into a corner. As such, the clar itself is small and
extensible while still bringing some nice features to the table.
Changes compared to v1:
- Convert the ctypes unit tests to use clar, as I had it lying around
anyway and René was asking for it.
- Adopt the AWK-based script by René, replacing the Python-based one.
This gets rid of the mandatory Python dependency and at the same
time also allows us to be more flexible going forward.
- Wire up the third party sources in our Makefiles such that they do
not get linted via hdr-check or Coccinelle.
- Fix t/Makefile to not pass GIT_TEST_OPTS to our unit tests. They
don't know how to handle those options, and clar-based tests error
out when they see unknown options.
- Adapt Documentation/technical/unit-tests.txt to mention the clar.
- Rebase the series onto the latest master at 406f326d27 (The second
batch, 2024-08-01) to avoid some conflicts.
- Cherry-pick a whitespace fix that otherwise makes git-apply(1)
unhappy. The CI job is still broken because the first patch that
imports clar continues to be broken. I've created an upstream PR.
- Cherry-pick a fix by Randall to make clar work on HP NonStop. The
fix has been proposed upstream in a PR.
With these changes, the CI jobs at GitLab all pass, except for the
whitespace and clang-format checks.
I've also Cc'd Ed, one of the maintainers of clar. I also noticed that
I'm a maintainer of that project back from my libgit2 times, so I think
it should be relatively easy to land changes upstream.
Thanks!
Patrick
Patrick Steinhardt (7):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix whitespace errors
t/clar: fix compatibility with NonStop
Makefile: wire up the clar unit testing framework
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 42 +-
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++-
t/unit-tests/unit-test.c | 17 +
t/unit-tests/unit-test.h | 3 +
29 files changed, 3159 insertions(+), 98 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v1:
-: ---------- > 1: 78a9cc1162 t: do not pass GIT_TEST_OPTS to unit tests with prove
1: 4e3862991a ! 2: 6a88cf22a5 t: import the clar unit testing framework
@@ Commit message
Signed-off-by: Patrick Steinhardt <ps@pks.im>
+ ## Documentation/technical/unit-tests.txt ##
+@@ Documentation/technical/unit-tests.txt: GitHub / GitLab stars to estimate this.
+ :criterion: https://github.com/Snaipe/Criterion[Criterion]
+ :c-tap: https://github.com/rra/c-tap-harness/[C TAP]
+ :check: https://libcheck.github.io/check/[Check]
++:clar: https://github.com/clar-test/clar[Clar]
+
+ [format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
+ |=====
+@@ Documentation/technical/unit-tests.txt: Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
+ {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
+ {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
+ {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
++{clar},{lgpl},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
+ |=====
+
+ === Additional framework candidates
+
+ ## Makefile ##
+@@ Makefile: THIRD_PARTY_SOURCES += compat/poll/%
+ THIRD_PARTY_SOURCES += compat/regex/%
+ THIRD_PARTY_SOURCES += sha1collisiondetection/%
+ THIRD_PARTY_SOURCES += sha1dc/%
++THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
++THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+
+ UNIT_TEST_PROGRAMS += t-ctype
+ UNIT_TEST_PROGRAMS += t-example-decorate
+@@ Makefile: $(SP_OBJ): %.sp: %.c %.o
+ .PHONY: sparse
+ sparse: $(SP_OBJ)
+
+-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
++EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
+ ifndef OPENSSL_SHA1
+ EXCEPT_HDRS += sha1/openssl.h
+ endif
+
## t/unit-tests/clar/.github/workflows/ci.yml (new) ##
@@
+name: CI
-: ---------- > 3: a52ee59bf4 t/clar: fix whitespace errors
-: ---------- > 4: 02fb86dfbc t/clar: fix compatibility with NonStop
2: 7a5dfd5065 ! 5: 848dc673c4 Makefile: wire up the clar unit testing framework
@@ .gitignore
/bin-wrappers/
## Makefile ##
-@@ Makefile: THIRD_PARTY_SOURCES += compat/regex/%
- THIRD_PARTY_SOURCES += sha1collisiondetection/%
- THIRD_PARTY_SOURCES += sha1dc/%
+@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
+ THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+ THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
@@ Makefile: endif
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
+@@ Makefile: $(SP_OBJ): %.sp: %.c %.o
+ .PHONY: sparse
+ sparse: $(SP_OBJ)
+
+-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
++EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
+ ifndef OPENSSL_SHA1
+ EXCEPT_HDRS += sha1/openssl.h
+ endif
@@ Makefile: endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
@@ Makefile: cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
-+ $(RM) $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/.clarcache
++ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ Makefile: $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
-+ rm -f $(UNIT_TESTS_DIR)/.clarcache; \
+ fi
+
-+$(UNIT_TEST_DIR)/clar.suite: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
-+ $(QUIET_GEN)$(UNIT_TEST_DIR)/clar/generate.py $(UNIT_TEST_DIR) >/dev/null
-+ @touch $@
-+$(UNIT_TEST_DIR)/clar-decls.h: $(UNIT_TEST_DIR)/clar.suite
-+ $(QUIET_GEN)grep '^extern void' $^ >$@
++$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
++ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
++ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
++ done >$@
++$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
++ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) -I$(UNIT_TEST_DIR)/clar
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
@@ t/Makefile: CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard cha
## t/unit-tests/.gitignore ##
@@
/bin
-+/.clarcache
+/clar.suite
+/clar-decls.h
+ ## t/unit-tests/clar-generate.awk (new) ##
+@@
++function add_suite(suite, initialize, cleanup, count) {
++ if (!suite) return
++ suite_count++
++ callback_count += count
++ suites = suites " {\n"
++ suites = suites " \"" suite "\",\n"
++ suites = suites " " initialize ",\n"
++ suites = suites " " cleanup ",\n"
++ suites = suites " _clar_cb_" suite ", " count ", 1\n"
++ suites = suites " },\n"
++}
++
++BEGIN {
++ suites = "static struct clar_suite _clar_suites[] = {\n"
++}
++
++{
++ print
++ name = $3; sub(/\(.*$/, "", name)
++ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
++ short_name = name; sub(/^.*__/, "", short_name)
++ cb = "{ \"" short_name "\", &" name " }"
++ if (suite != prev_suite) {
++ add_suite(prev_suite, initialize, cleanup, count)
++ if (callbacks) callbacks = callbacks "};\n"
++ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
++ initialize = "{ NULL, NULL }"
++ cleanup = "{ NULL, NULL }"
++ count = 0
++ prev_suite = suite
++ }
++ if (short_name == "initialize") {
++ initialize = cb
++ } else if (short_name == "cleanup") {
++ cleanup = cb
++ } else {
++ callbacks = callbacks " " cb ",\n"
++ count++
++ }
++}
++
++END {
++ add_suite(suite, initialize, cleanup, count)
++ suites = suites "};"
++ if (callbacks) callbacks = callbacks "};"
++ print callbacks
++ print suites
++ print "static const size_t _clar_suite_count = " suite_count ";"
++ print "static const size_t _clar_callback_count = " callback_count ";"
++}
+
## t/unit-tests/unit-test.c (new) ##
@@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
-+ const char **args;
++ const char **argv_copy;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
-+ DUP_ARRAY(args, argv, argc + 1);
-+ args[argc++] = "-t";
++ ALLOC_ARRAY(argv_copy, argc + 2);
++ COPY_ARRAY(argv_copy, argv, argc);
++ argv_copy[argc++] = "-t";
++ argv_copy[argc] = NULL;
+
-+ ret = clar_test(argc, (char **) args);
++ ret = clar_test(argc, (char **) argv_copy);
+
-+ free(args);
++ free(argv_copy);
+ return ret;
+}
3: 1c2a510547 ! 6: 578e657269 t/unit-tests: convert strvec tests to use clar
@@ Commit message
Signed-off-by: Patrick Steinhardt <ps@pks.im>
## Makefile ##
-@@ Makefile: THIRD_PARTY_SOURCES += compat/regex/%
- THIRD_PARTY_SOURCES += sha1collisiondetection/%
- THIRD_PARTY_SOURCES += sha1dc/%
+@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
+ THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+ THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
-@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-basics
+@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
@@ t/unit-tests/t-strvec.c => t/unit-tests/strvec.c
#include "strvec.h"
#define check_strvec(vec, ...) \
-- check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__)
-+ check_strvec_loc(__FILE__, __func__, __LINE__, vec, __VA_ARGS__)
- LAST_ARG_MUST_BE_NULL
--static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
-+static void check_strvec_loc(const char *file, const char *func, size_t line, struct strvec *vec, ...)
- {
- va_list ap;
- size_t nr = 0;
-@@ t/unit-tests/strvec.c: static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
- if (!str)
- break;
-
-- if (!check_uint(vec->nr, >, nr) ||
-- !check_uint(vec->alloc, >, nr) ||
-- !check_str(vec->v[nr], str)) {
-- struct strbuf msg = STRBUF_INIT;
-- strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
-- test_assert(loc, msg.buf, 0);
-- strbuf_release(&msg);
-- va_end(ap);
-- return;
-- }
-+ clar__assert(vec->nr > nr, file, func, line, "Vector size too small", NULL, 1);
-+ clar__assert(vec->alloc > nr, file, func, line, "Vector allocation too small", NULL, 1);
-+ cl_assert_equal_s(vec->v[nr], str);
-
- nr++;
- }
- va_end(ap);
-
-- check_uint(vec->nr, ==, nr);
-- check_uint(vec->alloc, >=, nr);
-- check_pointer_eq(vec->v[nr], NULL);
-+ cl_assert(vec->nr == nr);
-+ cl_assert(vec->alloc >= nr);
-+ cl_assert_equal_p(vec->v[nr], NULL);
- }
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
+- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
+- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
+- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
+- check_uint((vec)->nr, <=, (vec)->alloc)) { \
+- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
+- if (!check_str((vec)->v[i], expect[i])) { \
+- test_msg(" i: %"PRIuMAX, \
+- (uintmax_t)i); \
+- break; \
+- } \
+- } \
+- } \
++ cl_assert(ARRAY_SIZE(expect) > 0); \
++ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
++ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
++ cl_assert((vec)->nr <= (vec)->alloc); \
++ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
++ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
-static void t_static_init(void)
+void test_strvec__init(void)
@@ t/unit-tests/strvec.c: static void t_push(void)
}
-static void t_pushf(void)
-+void test_strvec__pushft_pushf(void)
++void test_strvec__pushf(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushf(&vec, "foo: %d", 1);
@@ t/unit-tests/strvec.c: static void t_detach(void)
- TEST(t_detach(), "detach");
- return test_done();
-}
+
+ ## t/unit-tests/unit-test.c ##
+@@ t/unit-tests/unit-test.c: int cmd_main(int argc, const char **argv)
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+- ALLOC_ARRAY(argv_copy, argc + 2);
++ ALLOC_ARRAY(argv_copy, argc + 1);
+ COPY_ARRAY(argv_copy, argv, argc);
+ argv_copy[argc++] = "-t";
+- argv_copy[argc] = NULL;
+
+ ret = clar_test(argc, (char **) argv_copy);
+
-: ---------- > 7: 238de33b93 t/unit-tests: convert ctype tests to use clar
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH v2 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 2/7] t: import the clar unit testing framework Patrick Steinhardt
` (5 subsequent siblings)
6 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 1951 bytes --]
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06f..d2212de0b7 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b..63328ac630 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v2 2/7] t: import the clar unit testing framework
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 22:18 ` Josh Steadmon
2024-08-06 14:14 ` [RFC PATCH v2 3/7] t/clar: fix whitespace errors Patrick Steinhardt
` (4 subsequent siblings)
6 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 88170 bytes --]
Import the clar unit testing framework at commit faa8419 (Merge pull
request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 154 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 267 +++++++
t/unit-tests/clar/test/.gitignore | 5 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2946 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb1..fc201d4b3d 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{lgpl},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index 3863e60b66..674b0ac4e1 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3260,7 +3262,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 0000000000..b1ac2de460
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 0000000000..8983817f0c
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 0000000000..a8961c5f10
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 0000000000..3fc2c76815
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 0000000000..8c22382bd5
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 0000000000..6ec6423484
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 0000000000..a6eda5e5dc
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 0000000000..c17e2f693b
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 0000000000..0ba1479620
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,154 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
+
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 0000000000..4dd352e28b
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 0000000000..931b4d60d9
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
+
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 0000000000..3e8ae0a3a5
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 0000000000..93c6b2ad32
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 0000000000..0fcaa639aa
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 0000000000..59e56ad255
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 0000000000..a4d91b72fa
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 0000000000..220f4aa98a
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 0000000000..faa1209262
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v2 2/7] t: import the clar unit testing framework
2024-08-06 14:14 ` [RFC PATCH v2 2/7] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-06 22:18 ` Josh Steadmon
2024-08-07 5:52 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Josh Steadmon @ 2024-08-06 22:18 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood, rsbecker, Edward Thomson
On 2024.08.06 16:14, Patrick Steinhardt wrote:
> Import the clar unit testing framework at commit faa8419 (Merge pull
> request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
> will be wired up in subsequent commits.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> Documentation/technical/unit-tests.txt | 2 +
> Makefile | 4 +-
> t/unit-tests/clar/.github/workflows/ci.yml | 23 +
> t/unit-tests/clar/COPYING | 15 +
> t/unit-tests/clar/README.md | 329 ++++++++
> t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
> t/unit-tests/clar/clar.h | 173 +++++
> t/unit-tests/clar/clar/fixtures.h | 50 ++
> t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
> t/unit-tests/clar/clar/print.h | 211 ++++++
> t/unit-tests/clar/clar/sandbox.h | 154 ++++
> t/unit-tests/clar/clar/summary.h | 143 ++++
> t/unit-tests/clar/generate.py | 267 +++++++
> t/unit-tests/clar/test/.gitignore | 5 +
> t/unit-tests/clar/test/Makefile | 39 +
> t/unit-tests/clar/test/clar_test.h | 16 +
> t/unit-tests/clar/test/main.c | 40 +
> t/unit-tests/clar/test/main.c.sample | 27 +
> t/unit-tests/clar/test/resources/test/file | 1 +
> t/unit-tests/clar/test/sample.c | 84 ++
> 20 files changed, 2946 insertions(+), 1 deletion(-)
> create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
> create mode 100644 t/unit-tests/clar/COPYING
> create mode 100644 t/unit-tests/clar/README.md
> create mode 100644 t/unit-tests/clar/clar.c
> create mode 100644 t/unit-tests/clar/clar.h
> create mode 100644 t/unit-tests/clar/clar/fixtures.h
> create mode 100644 t/unit-tests/clar/clar/fs.h
> create mode 100644 t/unit-tests/clar/clar/print.h
> create mode 100644 t/unit-tests/clar/clar/sandbox.h
> create mode 100644 t/unit-tests/clar/clar/summary.h
> create mode 100755 t/unit-tests/clar/generate.py
> create mode 100644 t/unit-tests/clar/test/.gitignore
> create mode 100644 t/unit-tests/clar/test/Makefile
> create mode 100644 t/unit-tests/clar/test/clar_test.h
> create mode 100644 t/unit-tests/clar/test/main.c
> create mode 100644 t/unit-tests/clar/test/main.c.sample
> create mode 100644 t/unit-tests/clar/test/resources/test/file
> create mode 100644 t/unit-tests/clar/test/sample.c
>
> diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
> index 206037ffb1..fc201d4b3d 100644
> --- a/Documentation/technical/unit-tests.txt
> +++ b/Documentation/technical/unit-tests.txt
> @@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
> :criterion: https://github.com/Snaipe/Criterion[Criterion]
> :c-tap: https://github.com/rra/c-tap-harness/[C TAP]
> :check: https://libcheck.github.io/check/[Check]
> +:clar: https://github.com/clar-test/clar[Clar]
>
> [format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
> |=====
> @@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
> {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
> {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
> {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
> +{clar},{lgpl},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
> |=====
Isn't clar ISC-licensed, not LGPL?
https://github.com/clar-test/clar/blob/main/COPYING
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v2 2/7] t: import the clar unit testing framework
2024-08-06 22:18 ` Josh Steadmon
@ 2024-08-07 5:52 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-07 5:52 UTC (permalink / raw)
To: Josh Steadmon, git, René Scharfe, Junio C Hamano,
Kyle Lippincott, Phillip Wood, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 857 bytes --]
On Tue, Aug 06, 2024 at 03:18:25PM -0700, Josh Steadmon wrote:
> On 2024.08.06 16:14, Patrick Steinhardt wrote:
> > @@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
> > {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
> > {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
> > {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
> > +{clar},{lgpl},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
> > |=====
>
> Isn't clar ISC-licensed, not LGPL?
> https://github.com/clar-test/clar/blob/main/COPYING
Yes it is, and that's also what I said in other mails. Guess this here
was a copy-paste error, thanks for spotting it!
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH v2 3/7] t/clar: fix whitespace errors
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 2/7] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 4/7] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (3 subsequent siblings)
6 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 1717 bytes --]
Fix whitespace errors in the clar that make git-apply(1) unhappy. This
has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/97
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 5 ++---
t/unit-tests/clar/generate.py | 1 -
t/unit-tests/clar/test/.gitignore | 1 -
3 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..7c177f3525 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -25,9 +25,9 @@ find_tmp_path(char *buffer, size_t length)
static const size_t var_count = 5;
static const char *env_vars[] = {
"CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
- };
+ };
- size_t i;
+ size_t i;
for (i = 0; i < var_count; ++i) {
const char *env = getenv(env_vars[i]);
@@ -151,4 +151,3 @@ const char *clar_sandbox_path(void)
{
return _clar_path;
}
-
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
index 931b4d60d9..80996ac3e7 100755
--- a/t/unit-tests/clar/generate.py
+++ b/t/unit-tests/clar/generate.py
@@ -264,4 +264,3 @@ def write(self):
suite.disable(options.excluded)
if suite.write():
print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
-
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
index 3e8ae0a3a5..a477d0c40c 100644
--- a/t/unit-tests/clar/test/.gitignore
+++ b/t/unit-tests/clar/test/.gitignore
@@ -2,4 +2,3 @@ clar.suite
.clarcache
clar_test
*.o
-
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v2 4/7] t/clar: fix compatibility with NonStop
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
` (2 preceding siblings ...)
2024-08-06 14:14 ` [RFC PATCH v2 3/7] t/clar: fix whitespace errors Patrick Steinhardt
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 5/7] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (2 subsequent siblings)
6 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 975 bytes --]
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f3525..e25057b7c4 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v2 5/7] Makefile: wire up the clar unit testing framework
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
` (3 preceding siblings ...)
2024-08-06 14:14 ` [RFC PATCH v2 4/7] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 14:14 ` [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
2024-08-06 14:15 ` [RFC PATCH v2 7/7] t/unit-tests: convert ctype " Patrick Steinhardt
6 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 10082 bytes --]
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 36 ++++++++++++++++++++----
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 18 ++++++++++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 106 insertions(+), 5 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c2..6687bd6db4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 674b0ac4e1..f56b14ad6d 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,6 +1334,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2713,6 +2718,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3215,7 +3221,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3262,7 +3268,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
@@ -3646,7 +3652,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3702,6 +3708,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3861,7 +3868,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) -I$(UNIT_TEST_DIR)/clar
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b7..131ffd778f 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec..d0632ec7f9 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 0000000000..ab71ce6c9f
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 0000000000..32a81299e9
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,18 @@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **argv_copy;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+ ALLOC_ARRAY(argv_copy, argc + 2);
+ COPY_ARRAY(argv_copy, argv, argc);
+ argv_copy[argc++] = "-t";
+ argv_copy[argc] = NULL;
+
+ ret = clar_test(argc, (char **) argv_copy);
+
+ free(argv_copy);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 0000000000..99d59df1b0
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar.h"
+#include "clar-decls.h"
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
` (4 preceding siblings ...)
2024-08-06 14:14 ` [RFC PATCH v2 5/7] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-08-06 14:14 ` Patrick Steinhardt
2024-08-06 23:05 ` Josh Steadmon
2024-08-06 14:15 ` [RFC PATCH v2 7/7] t/unit-tests: convert ctype " Patrick Steinhardt
6 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 12297 bytes --]
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++++++++++----------------
t/unit-tests/unit-test.c | 3 +-
3 files changed, 45 insertions(+), 79 deletions(-)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
diff --git a/Makefile b/Makefile
index f56b14ad6d..cf76f9b353 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,6 +1334,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1351,7 +1352,6 @@ UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
similarity index 54%
rename from t/unit-tests/t-strvec.c
rename to t/unit-tests/strvec.c
index fa1a041469..d782c5f73b 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/strvec.c
@@ -1,52 +1,46 @@
-#include "test-lib.h"
+#include "unit-test.h"
#include "strbuf.h"
#include "strvec.h"
#define check_strvec(vec, ...) \
do { \
const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
} while (0)
-static void t_static_init(void)
+void test_strvec__init(void)
{
struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
}
-static void t_dynamic_init(void)
+void test_strvec__dynamic_init(void)
{
struct strvec vec;
strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
}
-static void t_clear(void)
+void test_strvec__clear(void)
{
struct strvec vec = STRVEC_INIT;
strvec_push(&vec, "foo");
strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
}
-static void t_push(void)
+void test_strvec__push(void)
{
struct strvec vec = STRVEC_INIT;
@@ -59,7 +53,7 @@ static void t_push(void)
strvec_clear(&vec);
}
-static void t_pushf(void)
+void test_strvec__pushf(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushf(&vec, "foo: %d", 1);
@@ -67,7 +61,7 @@ static void t_pushf(void)
strvec_clear(&vec);
}
-static void t_pushl(void)
+void test_strvec__pushl(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -75,7 +69,7 @@ static void t_pushl(void)
strvec_clear(&vec);
}
-static void t_pushv(void)
+void test_strvec__pushv(void)
{
const char *strings[] = {
"foo", "bar", "baz", NULL,
@@ -88,7 +82,7 @@ static void t_pushv(void)
strvec_clear(&vec);
}
-static void t_replace_at_head(void)
+void test_strvec__replace_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -97,7 +91,7 @@ static void t_replace_at_head(void)
strvec_clear(&vec);
}
-static void t_replace_at_tail(void)
+void test_strvec__replace_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -106,7 +100,7 @@ static void t_replace_at_tail(void)
strvec_clear(&vec);
}
-static void t_replace_in_between(void)
+void test_strvec__replace_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -115,7 +109,7 @@ static void t_replace_in_between(void)
strvec_clear(&vec);
}
-static void t_replace_with_substring(void)
+void test_strvec__replace_with_substring(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", NULL);
@@ -124,7 +118,7 @@ static void t_replace_with_substring(void)
strvec_clear(&vec);
}
-static void t_remove_at_head(void)
+void test_strvec__remove_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -133,7 +127,7 @@ static void t_remove_at_head(void)
strvec_clear(&vec);
}
-static void t_remove_at_tail(void)
+void test_strvec__remove_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -142,7 +136,7 @@ static void t_remove_at_tail(void)
strvec_clear(&vec);
}
-static void t_remove_in_between(void)
+void test_strvec__remove_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -151,7 +145,7 @@ static void t_remove_in_between(void)
strvec_clear(&vec);
}
-static void t_pop_empty_array(void)
+void test_strvec__pop_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pop(&vec);
@@ -159,7 +153,7 @@ static void t_pop_empty_array(void)
strvec_clear(&vec);
}
-static void t_pop_non_empty_array(void)
+void test_strvec__pop_non_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -168,7 +162,7 @@ static void t_pop_non_empty_array(void)
strvec_clear(&vec);
}
-static void t_split_empty_string(void)
+void test_strvec__split_empty_string(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "");
@@ -176,7 +170,7 @@ static void t_split_empty_string(void)
strvec_clear(&vec);
}
-static void t_split_single_item(void)
+void test_strvec__split_single_item(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo");
@@ -184,7 +178,7 @@ static void t_split_single_item(void)
strvec_clear(&vec);
}
-static void t_split_multiple_items(void)
+void test_strvec__split_multiple_items(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo bar baz");
@@ -192,7 +186,7 @@ static void t_split_multiple_items(void)
strvec_clear(&vec);
}
-static void t_split_whitespace_only(void)
+void test_strvec__split_whitespace_only(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, " \t\n");
@@ -200,7 +194,7 @@ static void t_split_whitespace_only(void)
strvec_clear(&vec);
}
-static void t_split_multiple_consecutive_whitespaces(void)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo\n\t bar");
@@ -208,7 +202,7 @@ static void t_split_multiple_consecutive_whitespaces(void)
strvec_clear(&vec);
}
-static void t_detach(void)
+void test_strvec__detach(void)
{
struct strvec vec = STRVEC_INIT;
const char **detached;
@@ -216,40 +210,13 @@ static void t_detach(void)
strvec_push(&vec, "foo");
detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert(vec.nr == 0);
+ cl_assert(vec.alloc == 0);
free((char *) detached[0]);
free(detached);
}
-
-int cmd_main(int argc, const char **argv)
-{
- TEST(t_static_init(), "static initialization");
- TEST(t_dynamic_init(), "dynamic initialization");
- TEST(t_clear(), "clear");
- TEST(t_push(), "push");
- TEST(t_pushf(), "pushf");
- TEST(t_pushl(), "pushl");
- TEST(t_pushv(), "pushv");
- TEST(t_replace_at_head(), "replace at head");
- TEST(t_replace_in_between(), "replace in between");
- TEST(t_replace_at_tail(), "replace at tail");
- TEST(t_replace_with_substring(), "replace with substring");
- TEST(t_remove_at_head(), "remove at head");
- TEST(t_remove_in_between(), "remove in between");
- TEST(t_remove_at_tail(), "remove at tail");
- TEST(t_pop_empty_array(), "pop with empty array");
- TEST(t_pop_non_empty_array(), "pop with non-empty array");
- TEST(t_split_empty_string(), "split empty string");
- TEST(t_split_single_item(), "split single item");
- TEST(t_split_multiple_items(), "split multiple items");
- TEST(t_split_whitespace_only(), "split whitespace only");
- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
- TEST(t_detach(), "detach");
- return test_done();
-}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 32a81299e9..82b7635e6a 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
int ret;
/* Append the "-t" flag such that the tests generate TAP output. */
- ALLOC_ARRAY(argv_copy, argc + 2);
+ ALLOC_ARRAY(argv_copy, argc + 1);
COPY_ARRAY(argv_copy, argv, argc);
argv_copy[argc++] = "-t";
- argv_copy[argc] = NULL;
ret = clar_test(argc, (char **) argv_copy);
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar
2024-08-06 14:14 ` [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-06 23:05 ` Josh Steadmon
2024-08-07 5:52 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Josh Steadmon @ 2024-08-06 23:05 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood, rsbecker, Edward Thomson
On 2024.08.06 16:14, Patrick Steinhardt wrote:
> Convert the strvec tests to use the new clar unit testing framework.
> This is a first test balloon that demonstrates how the testing infra for
> clar-based tests looks like.
>
> The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
> running that binary, it generates TAP output:
>
> # ./t/unit-tests/bin/unit-tests
> TAP version 13
> # start of suite 1: strvec
> ok 1 - strvec::init
> ok 2 - strvec::dynamic_init
> ok 3 - strvec::clear
> ok 4 - strvec::push
> ok 5 - strvec::pushft_pushf
> ok 6 - strvec::pushl
> ok 7 - strvec::pushv
> ok 8 - strvec::replace_at_head
> ok 9 - strvec::replace_at_tail
> ok 10 - strvec::replace_in_between
> ok 11 - strvec::replace_with_substring
> ok 12 - strvec::remove_at_head
> ok 13 - strvec::remove_at_tail
> ok 14 - strvec::remove_in_between
> ok 15 - strvec::pop_empty_array
> ok 16 - strvec::pop_non_empty_array
> ok 17 - strvec::split_empty_string
> ok 18 - strvec::split_single_item
> ok 19 - strvec::split_multiple_items
> ok 20 - strvec::split_whitespace_only
> ok 21 - strvec::split_multiple_consecutive_whitespaces
> ok 22 - strvec::detach
> 1..22
>
> The binary also supports some parameters that allow us to run only a
> subset of unit tests or alter the output:
>
> $ ./t/unit-tests/bin/unit-tests -h
> Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
> -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)
>
> Furthermore, running `make unit-tests` runs the binary along with all
> the other unit tests we have.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> Makefile | 2 +-
> t/unit-tests/{t-strvec.c => strvec.c} | 119 ++++++++++----------------
> t/unit-tests/unit-test.c | 3 +-
> 3 files changed, 45 insertions(+), 79 deletions(-)
> rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
>
> diff --git a/Makefile b/Makefile
> index f56b14ad6d..cf76f9b353 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1334,6 +1334,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
> THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
> THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
>
> +UNIT_TESTS_SUITES += strvec
> UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
> UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
> UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
> @@ -1351,7 +1352,6 @@ UNIT_TEST_PROGRAMS += t-reftable-merged
> UNIT_TEST_PROGRAMS += t-reftable-record
> UNIT_TEST_PROGRAMS += t-strbuf
> UNIT_TEST_PROGRAMS += t-strcmp-offset
> -UNIT_TEST_PROGRAMS += t-strvec
> UNIT_TEST_PROGRAMS += t-trailer
> UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
> UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
> similarity index 54%
> rename from t/unit-tests/t-strvec.c
> rename to t/unit-tests/strvec.c
> index fa1a041469..d782c5f73b 100644
> --- a/t/unit-tests/t-strvec.c
> +++ b/t/unit-tests/strvec.c
> @@ -1,52 +1,46 @@
> -#include "test-lib.h"
> +#include "unit-test.h"
> #include "strbuf.h"
> #include "strvec.h"
>
> #define check_strvec(vec, ...) \
> do { \
> const char *expect[] = { __VA_ARGS__ }; \
> - if (check_uint(ARRAY_SIZE(expect), >, 0) && \
> - check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
> - check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
> - check_uint((vec)->nr, <=, (vec)->alloc)) { \
> - for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
> - if (!check_str((vec)->v[i], expect[i])) { \
> - test_msg(" i: %"PRIuMAX, \
> - (uintmax_t)i); \
> - break; \
> - } \
> - } \
> - } \
> + cl_assert(ARRAY_SIZE(expect) > 0); \
> + cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
> + cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
> + cl_assert((vec)->nr <= (vec)->alloc); \
> + for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
> + cl_assert_equal_s((vec)->v[i], expect[i]); \
> } while (0)
>
> -static void t_static_init(void)
> +void test_strvec__init(void)
> {
> struct strvec vec = STRVEC_INIT;
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert(vec.nr == 0);
> + cl_assert(vec.alloc == 0);
Is there a reason you used cl_assert() instead of cl_assert_equal_i()
for the .nr and .alloc checks here and below?
> }
>
> -static void t_dynamic_init(void)
> +void test_strvec__dynamic_init(void)
> {
> struct strvec vec;
> strvec_init(&vec);
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert(vec.nr == 0);
> + cl_assert(vec.alloc == 0);
> }
>
> -static void t_clear(void)
> +void test_strvec__clear(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_push(&vec, "foo");
> strvec_clear(&vec);
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert(vec.nr == 0);
> + cl_assert(vec.alloc == 0);
> }
>
> -static void t_push(void)
> +void test_strvec__push(void)
> {
> struct strvec vec = STRVEC_INIT;
>
> @@ -59,7 +53,7 @@ static void t_push(void)
> strvec_clear(&vec);
> }
>
> -static void t_pushf(void)
> +void test_strvec__pushf(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushf(&vec, "foo: %d", 1);
> @@ -67,7 +61,7 @@ static void t_pushf(void)
> strvec_clear(&vec);
> }
>
> -static void t_pushl(void)
> +void test_strvec__pushl(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -75,7 +69,7 @@ static void t_pushl(void)
> strvec_clear(&vec);
> }
>
> -static void t_pushv(void)
> +void test_strvec__pushv(void)
> {
> const char *strings[] = {
> "foo", "bar", "baz", NULL,
> @@ -88,7 +82,7 @@ static void t_pushv(void)
> strvec_clear(&vec);
> }
>
> -static void t_replace_at_head(void)
> +void test_strvec__replace_at_head(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -97,7 +91,7 @@ static void t_replace_at_head(void)
> strvec_clear(&vec);
> }
>
> -static void t_replace_at_tail(void)
> +void test_strvec__replace_at_tail(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -106,7 +100,7 @@ static void t_replace_at_tail(void)
> strvec_clear(&vec);
> }
>
> -static void t_replace_in_between(void)
> +void test_strvec__replace_in_between(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -115,7 +109,7 @@ static void t_replace_in_between(void)
> strvec_clear(&vec);
> }
>
> -static void t_replace_with_substring(void)
> +void test_strvec__replace_with_substring(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", NULL);
> @@ -124,7 +118,7 @@ static void t_replace_with_substring(void)
> strvec_clear(&vec);
> }
>
> -static void t_remove_at_head(void)
> +void test_strvec__remove_at_head(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -133,7 +127,7 @@ static void t_remove_at_head(void)
> strvec_clear(&vec);
> }
>
> -static void t_remove_at_tail(void)
> +void test_strvec__remove_at_tail(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -142,7 +136,7 @@ static void t_remove_at_tail(void)
> strvec_clear(&vec);
> }
>
> -static void t_remove_in_between(void)
> +void test_strvec__remove_in_between(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -151,7 +145,7 @@ static void t_remove_in_between(void)
> strvec_clear(&vec);
> }
>
> -static void t_pop_empty_array(void)
> +void test_strvec__pop_empty_array(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pop(&vec);
> @@ -159,7 +153,7 @@ static void t_pop_empty_array(void)
> strvec_clear(&vec);
> }
>
> -static void t_pop_non_empty_array(void)
> +void test_strvec__pop_non_empty_array(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> @@ -168,7 +162,7 @@ static void t_pop_non_empty_array(void)
> strvec_clear(&vec);
> }
>
> -static void t_split_empty_string(void)
> +void test_strvec__split_empty_string(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_split(&vec, "");
> @@ -176,7 +170,7 @@ static void t_split_empty_string(void)
> strvec_clear(&vec);
> }
>
> -static void t_split_single_item(void)
> +void test_strvec__split_single_item(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_split(&vec, "foo");
> @@ -184,7 +178,7 @@ static void t_split_single_item(void)
> strvec_clear(&vec);
> }
>
> -static void t_split_multiple_items(void)
> +void test_strvec__split_multiple_items(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_split(&vec, "foo bar baz");
> @@ -192,7 +186,7 @@ static void t_split_multiple_items(void)
> strvec_clear(&vec);
> }
>
> -static void t_split_whitespace_only(void)
> +void test_strvec__split_whitespace_only(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_split(&vec, " \t\n");
> @@ -200,7 +194,7 @@ static void t_split_whitespace_only(void)
> strvec_clear(&vec);
> }
>
> -static void t_split_multiple_consecutive_whitespaces(void)
> +void test_strvec__split_multiple_consecutive_whitespaces(void)
> {
> struct strvec vec = STRVEC_INIT;
> strvec_split(&vec, "foo\n\t bar");
> @@ -208,7 +202,7 @@ static void t_split_multiple_consecutive_whitespaces(void)
> strvec_clear(&vec);
> }
>
> -static void t_detach(void)
> +void test_strvec__detach(void)
> {
> struct strvec vec = STRVEC_INIT;
> const char **detached;
> @@ -216,40 +210,13 @@ static void t_detach(void)
> strvec_push(&vec, "foo");
>
> detached = strvec_detach(&vec);
> - check_str(detached[0], "foo");
> - check_pointer_eq(detached[1], NULL);
> + cl_assert_equal_s(detached[0], "foo");
> + cl_assert_equal_p(detached[1], NULL);
>
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert(vec.nr == 0);
> + cl_assert(vec.alloc == 0);
>
> free((char *) detached[0]);
> free(detached);
> }
> -
> -int cmd_main(int argc, const char **argv)
> -{
> - TEST(t_static_init(), "static initialization");
> - TEST(t_dynamic_init(), "dynamic initialization");
> - TEST(t_clear(), "clear");
> - TEST(t_push(), "push");
> - TEST(t_pushf(), "pushf");
> - TEST(t_pushl(), "pushl");
> - TEST(t_pushv(), "pushv");
> - TEST(t_replace_at_head(), "replace at head");
> - TEST(t_replace_in_between(), "replace in between");
> - TEST(t_replace_at_tail(), "replace at tail");
> - TEST(t_replace_with_substring(), "replace with substring");
> - TEST(t_remove_at_head(), "remove at head");
> - TEST(t_remove_in_between(), "remove in between");
> - TEST(t_remove_at_tail(), "remove at tail");
> - TEST(t_pop_empty_array(), "pop with empty array");
> - TEST(t_pop_non_empty_array(), "pop with non-empty array");
> - TEST(t_split_empty_string(), "split empty string");
> - TEST(t_split_single_item(), "split single item");
> - TEST(t_split_multiple_items(), "split multiple items");
> - TEST(t_split_whitespace_only(), "split whitespace only");
> - TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
> - TEST(t_detach(), "detach");
> - return test_done();
> -}
> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
> index 32a81299e9..82b7635e6a 100644
> --- a/t/unit-tests/unit-test.c
> +++ b/t/unit-tests/unit-test.c
> @@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
> int ret;
>
> /* Append the "-t" flag such that the tests generate TAP output. */
> - ALLOC_ARRAY(argv_copy, argc + 2);
> + ALLOC_ARRAY(argv_copy, argc + 1);
> COPY_ARRAY(argv_copy, argv, argc);
> argv_copy[argc++] = "-t";
> - argv_copy[argc] = NULL;
>
> ret = clar_test(argc, (char **) argv_copy);
>
> --
> 2.46.0.dirty
>
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar
2024-08-06 23:05 ` Josh Steadmon
@ 2024-08-07 5:52 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-07 5:52 UTC (permalink / raw)
To: Josh Steadmon, git, René Scharfe, Junio C Hamano,
Kyle Lippincott, Phillip Wood, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 656 bytes --]
On Tue, Aug 06, 2024 at 04:05:46PM -0700, Josh Steadmon wrote:
> On 2024.08.06 16:14, Patrick Steinhardt wrote:
> > -static void t_static_init(void)
> > +void test_strvec__init(void)
> > {
> > struct strvec vec = STRVEC_INIT;
> > - check_pointer_eq(vec.v, empty_strvec);
> > - check_uint(vec.nr, ==, 0);
> > - check_uint(vec.alloc, ==, 0);
> > + cl_assert_equal_p(vec.v, empty_strvec);
> > + cl_assert(vec.nr == 0);
> > + cl_assert(vec.alloc == 0);
>
> Is there a reason you used cl_assert() instead of cl_assert_equal_i()
> for the .nr and .alloc checks here and below?
No, there isn't. Let me adapt these indeed, thanks!
Patrick
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH v2 7/7] t/unit-tests: convert ctype tests to use clar
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
` (5 preceding siblings ...)
2024-08-06 14:14 ` [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-06 14:15 ` Patrick Steinhardt
6 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-06 14:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 3548 bytes --]
Convert the ctype tests to use the new clar unit testing framework.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------
2 files changed, 59 insertions(+), 14 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
diff --git a/Makefile b/Makefile
index cf76f9b353..aa2bd5cec1 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,13 +1334,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-mem-pool
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 71%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index d6ac1fe678..311df3a539 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,18 +1,12 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- int skip = test__run_begin(); \
- if (!skip) { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ for (int i = 0; i < 256; i++) \
+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -33,21 +27,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v3 0/7] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (5 preceding siblings ...)
2024-08-06 14:14 ` [RFC PATCH v2 0/7] " Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (7 more replies)
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
` (4 subsequent siblings)
11 siblings, 8 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 8242 bytes --]
Hi,
this is the third version of my RFC patch series that introduces the
clar testing framework into our unit tests. The intent is to not have to
hand-craft all features of a proper unit testing framework, while still
not painting us into a corner. As such, the clar itself is small and
extensible while still bringing some nice features to the table.
Changes compared to v2:
- Fix a copy/paste error for the clar license. It's ISC, not LGPL.
- Include "clar.h" via "clar/clar.h" such that we do not have to add
"clar/" as in preprocessor include directive.
- Adapt strvec unit test to use `cl_assert_equal_i()` instead of
`cl_assert()`.
Thanks!
Patrick
Patrick Steinhardt (7):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix whitespace errors
t/clar: fix compatibility with NonStop
Makefile: wire up the clar unit testing framework
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 42 +-
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++-
t/unit-tests/unit-test.c | 17 +
t/unit-tests/unit-test.h | 3 +
29 files changed, 3159 insertions(+), 98 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v2:
1: 78a9cc1162 = 1: 78a9cc1162 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: 6a88cf22a5 ! 2: b6c066ee4e t: import the clar unit testing framework
@@ Documentation/technical/unit-tests.txt: Framework,"<<license,License>>","<<vendo
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
-+{clar},{lgpl},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
++{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
3: a52ee59bf4 = 3: 35682b7686 t/clar: fix whitespace errors
4: 02fb86dfbc = 4: 7a76c21bcb t/clar: fix compatibility with NonStop
5: 848dc673c4 ! 5: 68b3c65951 Makefile: wire up the clar unit testing framework
@@ Makefile: endif
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
-@@ Makefile: $(SP_OBJ): %.sp: %.c %.o
- .PHONY: sparse
- sparse: $(SP_OBJ)
-
--EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
-+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
- ifndef OPENSSL_SHA1
- EXCEPT_HDRS += sha1/openssl.h
- endif
@@ Makefile: endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
@@ Makefile: $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
-+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR) -I$(UNIT_TEST_DIR)/clar
++$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
@@ t/unit-tests/unit-test.c (new)
## t/unit-tests/unit-test.h (new) ##
@@
+#include "git-compat-util.h"
-+#include "clar.h"
++#include "clar/clar.h"
+#include "clar-decls.h"
6: 578e657269 ! 6: 4a0888380e t/unit-tests: convert strvec tests to use clar
@@ t/unit-tests/t-strvec.c => t/unit-tests/strvec.c
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
-+ cl_assert(vec.nr == 0);
-+ cl_assert(vec.alloc == 0);
++ cl_assert_equal_i(vec.nr, 0);
++ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_dynamic_init(void)
@@ t/unit-tests/t-strvec.c => t/unit-tests/strvec.c
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
-+ cl_assert(vec.nr == 0);
-+ cl_assert(vec.alloc == 0);
++ cl_assert_equal_i(vec.nr, 0);
++ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_clear(void)
@@ t/unit-tests/t-strvec.c => t/unit-tests/strvec.c
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
-+ cl_assert(vec.nr == 0);
-+ cl_assert(vec.alloc == 0);
++ cl_assert_equal_i(vec.nr, 0);
++ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_push(void)
@@ t/unit-tests/strvec.c: static void t_detach(void)
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
-+ cl_assert(vec.nr == 0);
-+ cl_assert(vec.alloc == 0);
++ cl_assert_equal_i(vec.nr, 0);
++ cl_assert_equal_i(vec.alloc, 0);
free((char *) detached[0]);
free(detached);
7: 238de33b93 = 7: f423b01c05 t/unit-tests: convert ctype tests to use clar
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH v3 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 2/7] t: import the clar unit testing framework Patrick Steinhardt
` (6 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 1951 bytes --]
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06f..d2212de0b7 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b..63328ac630 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v3 2/7] t: import the clar unit testing framework
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 3/7] t/clar: fix whitespace errors Patrick Steinhardt
` (5 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 88169 bytes --]
Import the clar unit testing framework at commit faa8419 (Merge pull
request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 154 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 267 +++++++
t/unit-tests/clar/test/.gitignore | 5 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2946 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb1..5a432b7b29 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index 3863e60b66..674b0ac4e1 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3260,7 +3262,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 0000000000..b1ac2de460
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 0000000000..8983817f0c
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 0000000000..a8961c5f10
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 0000000000..3fc2c76815
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 0000000000..8c22382bd5
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 0000000000..6ec6423484
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 0000000000..a6eda5e5dc
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 0000000000..c17e2f693b
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 0000000000..0ba1479620
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,154 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
+
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 0000000000..4dd352e28b
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 0000000000..931b4d60d9
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
+
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 0000000000..3e8ae0a3a5
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 0000000000..93c6b2ad32
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 0000000000..0fcaa639aa
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 0000000000..59e56ad255
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 0000000000..a4d91b72fa
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 0000000000..220f4aa98a
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 0000000000..faa1209262
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 2/7] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-13 15:25 ` Junio C Hamano
2024-08-08 5:38 ` [RFC PATCH v3 4/7] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (4 subsequent siblings)
7 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 1717 bytes --]
Fix whitespace errors in the clar that make git-apply(1) unhappy. This
has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/97
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 5 ++---
t/unit-tests/clar/generate.py | 1 -
t/unit-tests/clar/test/.gitignore | 1 -
3 files changed, 2 insertions(+), 5 deletions(-)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..7c177f3525 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -25,9 +25,9 @@ find_tmp_path(char *buffer, size_t length)
static const size_t var_count = 5;
static const char *env_vars[] = {
"CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
- };
+ };
- size_t i;
+ size_t i;
for (i = 0; i < var_count; ++i) {
const char *env = getenv(env_vars[i]);
@@ -151,4 +151,3 @@ const char *clar_sandbox_path(void)
{
return _clar_path;
}
-
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
index 931b4d60d9..80996ac3e7 100755
--- a/t/unit-tests/clar/generate.py
+++ b/t/unit-tests/clar/generate.py
@@ -264,4 +264,3 @@ def write(self):
suite.disable(options.excluded)
if suite.write():
print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
-
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
index 3e8ae0a3a5..a477d0c40c 100644
--- a/t/unit-tests/clar/test/.gitignore
+++ b/t/unit-tests/clar/test/.gitignore
@@ -2,4 +2,3 @@ clar.suite
.clarcache
clar_test
*.o
-
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-08 5:38 ` [RFC PATCH v3 3/7] t/clar: fix whitespace errors Patrick Steinhardt
@ 2024-08-13 15:25 ` Junio C Hamano
2024-08-13 15:31 ` rsbecker
2024-08-13 20:42 ` Junio C Hamano
0 siblings, 2 replies; 172+ messages in thread
From: Junio C Hamano @ 2024-08-13 15:25 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Patrick Steinhardt <ps@pks.im> writes:
> Fix whitespace errors in the clar that make git-apply(1) unhappy. This
> has been cherry-picked from the upstream pull request at [1].
>
> [1]: https://github.com/clar-test/clar/pull/97
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> t/unit-tests/clar/clar/sandbox.h | 5 ++---
> t/unit-tests/clar/generate.py | 1 -
> t/unit-tests/clar/test/.gitignore | 1 -
> 3 files changed, 2 insertions(+), 5 deletions(-)
A funny thing about this step is that the copy I carried for the
past few days was an empty patch, because out of habit I use the
"--whitespace=fix" option while running "git am". As that fixes
the whitespace breakage this step addresses while applying the
previous step, this one becomes empty.
I'll requeue these patches with --whitespace=warn instead.
Thanks.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-13 15:25 ` Junio C Hamano
@ 2024-08-13 15:31 ` rsbecker
2024-08-13 18:43 ` Junio C Hamano
2024-08-13 20:42 ` Junio C Hamano
1 sibling, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-13 15:31 UTC (permalink / raw)
To: 'Junio C Hamano', 'Patrick Steinhardt'
Cc: git, 'René Scharfe', 'Kyle Lippincott',
'Phillip Wood', 'Josh Steadmon',
'Edward Thomson'
On Tuesday, August 13, 2024 11:26 AM, Junio C Hamano wrote:
>Patrick Steinhardt <ps@pks.im> writes:
>
>> Fix whitespace errors in the clar that make git-apply(1) unhappy. This
>> has been cherry-picked from the upstream pull request at [1].
>>
>> [1]: https://github.com/clar-test/clar/pull/97
>>
>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>> ---
>> t/unit-tests/clar/clar/sandbox.h | 5 ++---
>> t/unit-tests/clar/generate.py | 1 -
>> t/unit-tests/clar/test/.gitignore | 1 -
>> 3 files changed, 2 insertions(+), 5 deletions(-)
>
>A funny thing about this step is that the copy I carried for the past few days was an
>empty patch, because out of habit I use the "--whitespace=fix" option while running
>"git am". As that fixes the whitespace breakage this step addresses while applying
>the previous step, this one becomes empty.
>
>I'll requeue these patches with --whitespace=warn instead.
Please forgive the third ask, but I really want to test the build early. Suggestions?
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-13 15:31 ` rsbecker
@ 2024-08-13 18:43 ` Junio C Hamano
2024-08-13 19:14 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-13 18:43 UTC (permalink / raw)
To: rsbecker
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon', 'Edward Thomson'
<rsbecker@nexbridge.com> writes:
> On Tuesday, August 13, 2024 11:26 AM, Junio C Hamano wrote:
>>Patrick Steinhardt <ps@pks.im> writes:
>>
>>> Fix whitespace errors in the clar that make git-apply(1) unhappy. This
>>> has been cherry-picked from the upstream pull request at [1].
>>>
>>> [1]: https://github.com/clar-test/clar/pull/97
>>>
>>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>>> ---
>>> t/unit-tests/clar/clar/sandbox.h | 5 ++---
>>> t/unit-tests/clar/generate.py | 1 -
>>> t/unit-tests/clar/test/.gitignore | 1 -
>>> 3 files changed, 2 insertions(+), 5 deletions(-)
>>
>>A funny thing about this step is that the copy I carried for the past few days was an
>>empty patch, because out of habit I use the "--whitespace=fix" option while running
>>"git am". As that fixes the whitespace breakage this step addresses while applying
>>the previous step, this one becomes empty.
>>
>>I'll requeue these patches with --whitespace=warn instead.
>
> Please forgive the third ask, but I really want to test the build early. Suggestions?
Sorry, but I do not follow. If you have "git am" and "git clone"
working, and if you have enough C toolchain to build the rest of
Git, wouldn't it be sufficient to also build this addition? I think
the 7 patches cleanly apply to any recent tip of 'master'.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-13 18:43 ` Junio C Hamano
@ 2024-08-13 19:14 ` rsbecker
0 siblings, 0 replies; 172+ messages in thread
From: rsbecker @ 2024-08-13 19:14 UTC (permalink / raw)
To: 'Junio C Hamano'
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon', 'Edward Thomson'
On Tuesday, August 13, 2024 2:44 PM, Junio C Hamano wrote:
><rsbecker@nexbridge.com> writes:
>
>> On Tuesday, August 13, 2024 11:26 AM, Junio C Hamano wrote:
>>>Patrick Steinhardt <ps@pks.im> writes:
>>>
>>>> Fix whitespace errors in the clar that make git-apply(1) unhappy.
>>>> This has been cherry-picked from the upstream pull request at [1].
>>>>
>>>> [1]: https://github.com/clar-test/clar/pull/97
>>>>
>>>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>>>> ---
>>>> t/unit-tests/clar/clar/sandbox.h | 5 ++---
>>>> t/unit-tests/clar/generate.py | 1 -
>>>> t/unit-tests/clar/test/.gitignore | 1 -
>>>> 3 files changed, 2 insertions(+), 5 deletions(-)
>>>
>>>A funny thing about this step is that the copy I carried for the past
>>>few days was an empty patch, because out of habit I use the
>>>"--whitespace=fix" option while running "git am". As that fixes the
>>>whitespace breakage this step addresses while applying the previous step, this
>one becomes empty.
>>>
>>>I'll requeue these patches with --whitespace=warn instead.
>>
>> Please forgive the third ask, but I really want to test the build early. Suggestions?
>
>Sorry, but I do not follow. If you have "git am" and "git clone"
>working, and if you have enough C toolchain to build the rest of Git, wouldn't it be
>sufficient to also build this addition? I think the 7 patches cleanly apply to any
>recent tip of 'master'.
I am confused. My bad. The unit test works fine with the custom clar off seen and next.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-13 15:25 ` Junio C Hamano
2024-08-13 15:31 ` rsbecker
@ 2024-08-13 20:42 ` Junio C Hamano
2024-08-14 5:58 ` Patrick Steinhardt
1 sibling, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-13 20:42 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Junio C Hamano <gitster@pobox.com> writes:
> Patrick Steinhardt <ps@pks.im> writes:
>
>> Fix whitespace errors in the clar that make git-apply(1) unhappy. This
>> has been cherry-picked from the upstream pull request at [1].
>>
>> [1]: https://github.com/clar-test/clar/pull/97
>>
>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>> ---
>> t/unit-tests/clar/clar/sandbox.h | 5 ++---
>> t/unit-tests/clar/generate.py | 1 -
>> t/unit-tests/clar/test/.gitignore | 1 -
>> 3 files changed, 2 insertions(+), 5 deletions(-)
>
> A funny thing about this step is that the copy I carried for the
> past few days was an empty patch, because out of habit I use the
> "--whitespace=fix" option while running "git am". As that fixes
> the whitespace breakage this step addresses while applying the
> previous step, this one becomes empty.
>
> I'll requeue these patches with --whitespace=warn instead.
Oops, it turns out that this is insufficient. I'll add the
following as a separate patch immediately after this step.
Thanks.
t/unit-tests/clar/clar/fs.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git c/t/unit-tests/clar/clar/fs.h w/t/unit-tests/clar/clar/fs.h
index a6eda5e5dc..3e39890bd3 100644
--- c/t/unit-tests/clar/clar/fs.h
+++ w/t/unit-tests/clar/clar/fs.h
@@ -146,7 +146,7 @@ fs_rm_wait(WCHAR *_wpath)
ERROR_PATH_NOT_FOUND == last_error)
return 0;
- Sleep(RM_RETRY_DELAY * retries * retries);
+ Sleep(RM_RETRY_DELAY * retries * retries);
}
while (retries++ <= RM_RETRY_COUNT);
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors
2024-08-13 20:42 ` Junio C Hamano
@ 2024-08-14 5:58 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-14 5:58 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
On Tue, Aug 13, 2024 at 01:42:08PM -0700, Junio C Hamano wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
> > Patrick Steinhardt <ps@pks.im> writes:
> >
> >> Fix whitespace errors in the clar that make git-apply(1) unhappy. This
> >> has been cherry-picked from the upstream pull request at [1].
> >>
> >> [1]: https://github.com/clar-test/clar/pull/97
> >>
> >> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> >> ---
> >> t/unit-tests/clar/clar/sandbox.h | 5 ++---
> >> t/unit-tests/clar/generate.py | 1 -
> >> t/unit-tests/clar/test/.gitignore | 1 -
> >> 3 files changed, 2 insertions(+), 5 deletions(-)
> >
> > A funny thing about this step is that the copy I carried for the
> > past few days was an empty patch, because out of habit I use the
> > "--whitespace=fix" option while running "git am". As that fixes
> > the whitespace breakage this step addresses while applying the
> > previous step, this one becomes empty.
> >
> > I'll requeue these patches with --whitespace=warn instead.
>
> Oops, it turns out that this is insufficient. I'll add the
> following as a separate patch immediately after this step.
>
> Thanks.
>
> t/unit-tests/clar/clar/fs.h | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git c/t/unit-tests/clar/clar/fs.h w/t/unit-tests/clar/clar/fs.h
> index a6eda5e5dc..3e39890bd3 100644
> --- c/t/unit-tests/clar/clar/fs.h
> +++ w/t/unit-tests/clar/clar/fs.h
> @@ -146,7 +146,7 @@ fs_rm_wait(WCHAR *_wpath)
> ERROR_PATH_NOT_FOUND == last_error)
> return 0;
>
> - Sleep(RM_RETRY_DELAY * retries * retries);
> + Sleep(RM_RETRY_DELAY * retries * retries);
> }
> while (retries++ <= RM_RETRY_COUNT);
Ah, didn't notice that one, thanks. I've also updated the upstream pull
request accordingly.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [RFC PATCH v3 4/7] t/clar: fix compatibility with NonStop
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
` (2 preceding siblings ...)
2024-08-08 5:38 ` [RFC PATCH v3 3/7] t/clar: fix whitespace errors Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 5/7] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (3 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 975 bytes --]
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f3525..e25057b7c4 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v3 5/7] Makefile: wire up the clar unit testing framework
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
` (3 preceding siblings ...)
2024-08-08 5:38 ` [RFC PATCH v3 4/7] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (2 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 9646 bytes --]
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 34 ++++++++++++++++++++---
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 18 ++++++++++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 105 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c2..6687bd6db4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 674b0ac4e1..d343703215 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,6 +1334,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2713,6 +2718,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3215,7 +3221,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3646,7 +3652,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3702,6 +3708,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3861,7 +3868,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b7..131ffd778f 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec..d0632ec7f9 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 0000000000..ab71ce6c9f
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 0000000000..32a81299e9
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,18 @@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **argv_copy;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+ ALLOC_ARRAY(argv_copy, argc + 2);
+ COPY_ARRAY(argv_copy, argv, argc);
+ argv_copy[argc++] = "-t";
+ argv_copy[argc] = NULL;
+
+ ret = clar_test(argc, (char **) argv_copy);
+
+ free(argv_copy);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 0000000000..66ec2387cc
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v3 6/7] t/unit-tests: convert strvec tests to use clar
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
` (4 preceding siblings ...)
2024-08-08 5:38 ` [RFC PATCH v3 5/7] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-08 5:38 ` [RFC PATCH v3 7/7] t/unit-tests: convert ctype " Patrick Steinhardt
2024-08-12 18:10 ` [RFC PATCH v3 0/7] Introduce clar testing framework Josh Steadmon
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 12345 bytes --]
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++++++++++----------------
t/unit-tests/unit-test.c | 3 +-
3 files changed, 45 insertions(+), 79 deletions(-)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
diff --git a/Makefile b/Makefile
index d343703215..ec90b8ac35 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,6 +1334,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1351,7 +1352,6 @@ UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
similarity index 54%
rename from t/unit-tests/t-strvec.c
rename to t/unit-tests/strvec.c
index fa1a041469..d11ed0f28d 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/strvec.c
@@ -1,52 +1,46 @@
-#include "test-lib.h"
+#include "unit-test.h"
#include "strbuf.h"
#include "strvec.h"
#define check_strvec(vec, ...) \
do { \
const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
} while (0)
-static void t_static_init(void)
+void test_strvec__init(void)
{
struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_dynamic_init(void)
+void test_strvec__dynamic_init(void)
{
struct strvec vec;
strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_clear(void)
+void test_strvec__clear(void)
{
struct strvec vec = STRVEC_INIT;
strvec_push(&vec, "foo");
strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_push(void)
+void test_strvec__push(void)
{
struct strvec vec = STRVEC_INIT;
@@ -59,7 +53,7 @@ static void t_push(void)
strvec_clear(&vec);
}
-static void t_pushf(void)
+void test_strvec__pushf(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushf(&vec, "foo: %d", 1);
@@ -67,7 +61,7 @@ static void t_pushf(void)
strvec_clear(&vec);
}
-static void t_pushl(void)
+void test_strvec__pushl(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -75,7 +69,7 @@ static void t_pushl(void)
strvec_clear(&vec);
}
-static void t_pushv(void)
+void test_strvec__pushv(void)
{
const char *strings[] = {
"foo", "bar", "baz", NULL,
@@ -88,7 +82,7 @@ static void t_pushv(void)
strvec_clear(&vec);
}
-static void t_replace_at_head(void)
+void test_strvec__replace_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -97,7 +91,7 @@ static void t_replace_at_head(void)
strvec_clear(&vec);
}
-static void t_replace_at_tail(void)
+void test_strvec__replace_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -106,7 +100,7 @@ static void t_replace_at_tail(void)
strvec_clear(&vec);
}
-static void t_replace_in_between(void)
+void test_strvec__replace_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -115,7 +109,7 @@ static void t_replace_in_between(void)
strvec_clear(&vec);
}
-static void t_replace_with_substring(void)
+void test_strvec__replace_with_substring(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", NULL);
@@ -124,7 +118,7 @@ static void t_replace_with_substring(void)
strvec_clear(&vec);
}
-static void t_remove_at_head(void)
+void test_strvec__remove_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -133,7 +127,7 @@ static void t_remove_at_head(void)
strvec_clear(&vec);
}
-static void t_remove_at_tail(void)
+void test_strvec__remove_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -142,7 +136,7 @@ static void t_remove_at_tail(void)
strvec_clear(&vec);
}
-static void t_remove_in_between(void)
+void test_strvec__remove_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -151,7 +145,7 @@ static void t_remove_in_between(void)
strvec_clear(&vec);
}
-static void t_pop_empty_array(void)
+void test_strvec__pop_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pop(&vec);
@@ -159,7 +153,7 @@ static void t_pop_empty_array(void)
strvec_clear(&vec);
}
-static void t_pop_non_empty_array(void)
+void test_strvec__pop_non_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -168,7 +162,7 @@ static void t_pop_non_empty_array(void)
strvec_clear(&vec);
}
-static void t_split_empty_string(void)
+void test_strvec__split_empty_string(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "");
@@ -176,7 +170,7 @@ static void t_split_empty_string(void)
strvec_clear(&vec);
}
-static void t_split_single_item(void)
+void test_strvec__split_single_item(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo");
@@ -184,7 +178,7 @@ static void t_split_single_item(void)
strvec_clear(&vec);
}
-static void t_split_multiple_items(void)
+void test_strvec__split_multiple_items(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo bar baz");
@@ -192,7 +186,7 @@ static void t_split_multiple_items(void)
strvec_clear(&vec);
}
-static void t_split_whitespace_only(void)
+void test_strvec__split_whitespace_only(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, " \t\n");
@@ -200,7 +194,7 @@ static void t_split_whitespace_only(void)
strvec_clear(&vec);
}
-static void t_split_multiple_consecutive_whitespaces(void)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo\n\t bar");
@@ -208,7 +202,7 @@ static void t_split_multiple_consecutive_whitespaces(void)
strvec_clear(&vec);
}
-static void t_detach(void)
+void test_strvec__detach(void)
{
struct strvec vec = STRVEC_INIT;
const char **detached;
@@ -216,40 +210,13 @@ static void t_detach(void)
strvec_push(&vec, "foo");
detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
free((char *) detached[0]);
free(detached);
}
-
-int cmd_main(int argc, const char **argv)
-{
- TEST(t_static_init(), "static initialization");
- TEST(t_dynamic_init(), "dynamic initialization");
- TEST(t_clear(), "clear");
- TEST(t_push(), "push");
- TEST(t_pushf(), "pushf");
- TEST(t_pushl(), "pushl");
- TEST(t_pushv(), "pushv");
- TEST(t_replace_at_head(), "replace at head");
- TEST(t_replace_in_between(), "replace in between");
- TEST(t_replace_at_tail(), "replace at tail");
- TEST(t_replace_with_substring(), "replace with substring");
- TEST(t_remove_at_head(), "remove at head");
- TEST(t_remove_in_between(), "remove in between");
- TEST(t_remove_at_tail(), "remove at tail");
- TEST(t_pop_empty_array(), "pop with empty array");
- TEST(t_pop_non_empty_array(), "pop with non-empty array");
- TEST(t_split_empty_string(), "split empty string");
- TEST(t_split_single_item(), "split single item");
- TEST(t_split_multiple_items(), "split multiple items");
- TEST(t_split_whitespace_only(), "split whitespace only");
- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
- TEST(t_detach(), "detach");
- return test_done();
-}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 32a81299e9..82b7635e6a 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
int ret;
/* Append the "-t" flag such that the tests generate TAP output. */
- ALLOC_ARRAY(argv_copy, argc + 2);
+ ALLOC_ARRAY(argv_copy, argc + 1);
COPY_ARRAY(argv_copy, argv, argc);
argv_copy[argc++] = "-t";
- argv_copy[argc] = NULL;
ret = clar_test(argc, (char **) argv_copy);
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [RFC PATCH v3 7/7] t/unit-tests: convert ctype tests to use clar
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
` (5 preceding siblings ...)
2024-08-08 5:38 ` [RFC PATCH v3 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-08 5:38 ` Patrick Steinhardt
2024-08-12 18:10 ` [RFC PATCH v3 0/7] Introduce clar testing framework Josh Steadmon
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-08 5:38 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
[-- Attachment #1: Type: text/plain, Size: 3548 bytes --]
Convert the ctype tests to use the new clar unit testing framework.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------
2 files changed, 59 insertions(+), 14 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
diff --git a/Makefile b/Makefile
index ec90b8ac35..fa7c540bc0 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,13 +1334,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-mem-pool
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 71%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index d6ac1fe678..311df3a539 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,18 +1,12 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- int skip = test__run_begin(); \
- if (!skip) { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ for (int i = 0; i < 256; i++) \
+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -33,21 +27,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
--
2.46.0.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 0/7] Introduce clar testing framework
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
` (6 preceding siblings ...)
2024-08-08 5:38 ` [RFC PATCH v3 7/7] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-08-12 18:10 ` Josh Steadmon
2024-08-12 18:13 ` rsbecker
2024-08-12 20:50 ` Junio C Hamano
7 siblings, 2 replies; 172+ messages in thread
From: Josh Steadmon @ 2024-08-12 18:10 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood, rsbecker, Edward Thomson
On 2024.08.08 07:38, Patrick Steinhardt wrote:
> Hi,
>
> this is the third version of my RFC patch series that introduces the
> clar testing framework into our unit tests. The intent is to not have to
> hand-craft all features of a proper unit testing framework, while still
> not painting us into a corner. As such, the clar itself is small and
> extensible while still bringing some nice features to the table.
>
> Changes compared to v2:
>
> - Fix a copy/paste error for the clar license. It's ISC, not LGPL.
>
> - Include "clar.h" via "clar/clar.h" such that we do not have to add
> "clar/" as in preprocessor include directive.
>
> - Adapt strvec unit test to use `cl_assert_equal_i()` instead of
> `cl_assert()`.
>
> Thanks!
>
> Patrick
>
> Patrick Steinhardt (7):
> t: do not pass GIT_TEST_OPTS to unit tests with prove
> t: import the clar unit testing framework
> t/clar: fix whitespace errors
> t/clar: fix compatibility with NonStop
> Makefile: wire up the clar unit testing framework
> t/unit-tests: convert strvec tests to use clar
> t/unit-tests: convert ctype tests to use clar
I'm generally in favor of this change, but I'm still unsure what our
plan is for importing this from upstream clar. Are we going to vendor
our own copy here and (hopefully) someone will pay attention to upstream
fixes and apply them to our copy? Or will we replace this with a
submodule?
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH v3 0/7] Introduce clar testing framework
2024-08-12 18:10 ` [RFC PATCH v3 0/7] Introduce clar testing framework Josh Steadmon
@ 2024-08-12 18:13 ` rsbecker
2024-08-12 20:50 ` Junio C Hamano
1 sibling, 0 replies; 172+ messages in thread
From: rsbecker @ 2024-08-12 18:13 UTC (permalink / raw)
To: 'Josh Steadmon', 'Patrick Steinhardt'
Cc: git, 'René Scharfe', 'Junio C Hamano',
'Kyle Lippincott', 'Phillip Wood',
'Edward Thomson'
On Monday, August 12, 2024 2:11 PM, Josh Steadmon wrote:
>On 2024.08.08 07:38, Patrick Steinhardt wrote:
>> Hi,
>>
>> this is the third version of my RFC patch series that introduces the
>> clar testing framework into our unit tests. The intent is to not have
>> to hand-craft all features of a proper unit testing framework, while
>> still not painting us into a corner. As such, the clar itself is small
>> and extensible while still bringing some nice features to the table.
>>
>> Changes compared to v2:
>>
>> - Fix a copy/paste error for the clar license. It's ISC, not LGPL.
>>
>> - Include "clar.h" via "clar/clar.h" such that we do not have to add
>> "clar/" as in preprocessor include directive.
>>
>> - Adapt strvec unit test to use `cl_assert_equal_i()` instead of
>> `cl_assert()`.
>>
>> Thanks!
>>
>> Patrick
>>
>> Patrick Steinhardt (7):
>> t: do not pass GIT_TEST_OPTS to unit tests with prove
>> t: import the clar unit testing framework
>> t/clar: fix whitespace errors
>> t/clar: fix compatibility with NonStop
>> Makefile: wire up the clar unit testing framework
>> t/unit-tests: convert strvec tests to use clar
>> t/unit-tests: convert ctype tests to use clar
>
>I'm generally in favor of this change, but I'm still unsure what our plan
is for
>importing this from upstream clar. Are we going to vendor our own copy here
and
>(hopefully) someone will pay attention to upstream fixes and apply them to
our
>copy? Or will we replace this with a submodule?
I think we have our own copy of clar, customized to integrate with the
existing make
infrastructure.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 0/7] Introduce clar testing framework
2024-08-12 18:10 ` [RFC PATCH v3 0/7] Introduce clar testing framework Josh Steadmon
2024-08-12 18:13 ` rsbecker
@ 2024-08-12 20:50 ` Junio C Hamano
2024-08-12 20:58 ` rsbecker
1 sibling, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-12 20:50 UTC (permalink / raw)
To: Josh Steadmon
Cc: Patrick Steinhardt, git, René Scharfe, Kyle Lippincott,
Phillip Wood, rsbecker, Edward Thomson
Josh Steadmon <steadmon@google.com> writes:
> I'm generally in favor of this change, but I'm still unsure what our
> plan is for importing this from upstream clar. Are we going to vendor
> our own copy here and (hopefully) someone will pay attention to upstream
> fixes and apply them to our copy? Or will we replace this with a
> submodule?
As long as we do not have to make any changes to the "vendored" code
ourselves, that would not matter. We will not randomly update the
gitlink that specifies "we want to use _this_ version and not other
version of upstream clar" without good reasons if you are using it
as a submodule, and we would need to justify why we are updating the
hierarchy if we import the hierarchy as vendored source. So the hassle
of "updating from upstream" is pretty much the same.
For something as small as "clar", I think it is fine to start with
the currently proposed layout and see what happens. If we can keep
going without touching the imported part of the sources at all, and
the system proves to be useful and stable, that is a good time to
suggest moving it out and binding the selected version of the
upstream as a submodule.
Thanks.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [RFC PATCH v3 0/7] Introduce clar testing framework
2024-08-12 20:50 ` Junio C Hamano
@ 2024-08-12 20:58 ` rsbecker
2024-08-12 22:13 ` Junio C Hamano
0 siblings, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-12 20:58 UTC (permalink / raw)
To: 'Junio C Hamano', 'Josh Steadmon'
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Edward Thomson'
On Monday, August 12, 2024 4:50 PM, Junio C Hamano wrote:
>Josh Steadmon <steadmon@google.com> writes:
>
>> I'm generally in favor of this change, but I'm still unsure what our
>> plan is for importing this from upstream clar. Are we going to vendor
>> our own copy here and (hopefully) someone will pay attention to
>> upstream fixes and apply them to our copy? Or will we replace this
>> with a submodule?
>
>As long as we do not have to make any changes to the "vendored" code ourselves,
>that would not matter. We will not randomly update the gitlink that specifies "we
>want to use _this_ version and not other version of upstream clar" without good
>reasons if you are using it as a submodule, and we would need to justify why we
>are updating the hierarchy if we import the hierarchy as vendored source. So the
>hassle of "updating from upstream" is pretty much the same.
>
>For something as small as "clar", I think it is fine to start with the currently proposed
>layout and see what happens. If we can keep going without touching the imported
>part of the sources at all, and the system proves to be useful and stable, that is a
>good time to suggest moving it out and binding the selected version of the
>upstream as a submodule.
I think we already have a copy customized for git's use. The main clar repo on its own
has portability issues. I have contributed a few fixes, but they need work.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 0/7] Introduce clar testing framework
2024-08-12 20:58 ` rsbecker
@ 2024-08-12 22:13 ` Junio C Hamano
2024-08-13 7:23 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-12 22:13 UTC (permalink / raw)
To: rsbecker
Cc: 'Josh Steadmon', 'Patrick Steinhardt', git,
'René Scharfe', 'Kyle Lippincott',
'Phillip Wood', 'Edward Thomson'
<rsbecker@nexbridge.com> writes:
>>For something as small as "clar", I think it is fine to start with the currently proposed
>>layout and see what happens. If we can keep going without touching the imported
>>part of the sources at all, and the system proves to be useful and stable, that is a
>>good time to suggest moving it out and binding the selected version of the
>>upstream as a submodule.
>
> I think we already have a copy customized for git's use. The main clar repo on its own
> has portability issues. I have contributed a few fixes, but they need work.
Yup, but as long as the changes we make are all upstreamable, the
story does not change all that much. Changes like "#ifdef TANDEM"
would be totally uncontroversial thing for them to accept and we
should be able to upstream them fairly easily, and once we thin our
local customization down to zero, we'd reach the state I outlined.
Starting out with a local copy helps us making these portability and
other changes without much friction, regardless of how responsive
the upstream is, and the request upstream would see is "here are the
changes to make it available on more platforms and/or making it
generally more useful. all of these changes have been used and
battle-tested in the context of the Git project for N months, please
apply."
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [RFC PATCH v3 0/7] Introduce clar testing framework
2024-08-12 22:13 ` Junio C Hamano
@ 2024-08-13 7:23 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-13 7:23 UTC (permalink / raw)
To: Junio C Hamano
Cc: rsbecker, 'Josh Steadmon', git,
'René Scharfe', 'Kyle Lippincott',
'Phillip Wood', 'Edward Thomson'
On Mon, Aug 12, 2024 at 03:13:39PM -0700, Junio C Hamano wrote:
> <rsbecker@nexbridge.com> writes:
>
> >>For something as small as "clar", I think it is fine to start with the currently proposed
> >>layout and see what happens. If we can keep going without touching the imported
> >>part of the sources at all, and the system proves to be useful and stable, that is a
> >>good time to suggest moving it out and binding the selected version of the
> >>upstream as a submodule.
> >
> > I think we already have a copy customized for git's use. The main clar repo on its own
> > has portability issues. I have contributed a few fixes, but they need work.
>
> Yup, but as long as the changes we make are all upstreamable, the
> story does not change all that much. Changes like "#ifdef TANDEM"
> would be totally uncontroversial thing for them to accept and we
> should be able to upstream them fairly easily, and once we thin our
> local customization down to zero, we'd reach the state I outlined.
>
> Starting out with a local copy helps us making these portability and
> other changes without much friction, regardless of how responsive
> the upstream is, and the request upstream would see is "here are the
> changes to make it available on more platforms and/or making it
> generally more useful. all of these changes have been used and
> battle-tested in the context of the Git project for N months, please
> apply."
Yeah, agreed. My intention certainly is to upstream all required
changes. Right now, it's only two minor fixes that we need, one for
NonStop and one for whitespace fixes.
While I'm still one of the maintainers of the clar, I first want to
connect with Ed before merging any of these fixes. I would otherwise
consider it a bit overreaching to just go in and merge things after
having been absent from that project for such a long time. But when
things are sorted out I think the upstreaming process should be easy
enough.
Meanwhile, I think keeping this in the tree is fine. We can then
optionally convert the code to a submodule once the necessary changes
are upstream.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v4 0/7] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (6 preceding siblings ...)
2024-08-08 5:38 ` [RFC PATCH v3 0/7] Introduce clar testing framework Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (7 more replies)
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (3 subsequent siblings)
11 siblings, 8 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Hi,
this is the fourth version of my patch series that introduces the clar
testing framework for our unit tests.
Changes compared to v3:
- Seeing that the overall feedback seems to be rather positive, I've
now dropped the "RFC" prefix.
- Fix another whitespace-damaged line in the clar unit test. I've
adapted the upstream pull request accordingly.
- Fix `make hdr-check`, which failed due to the missing dependency on
"clar-decls.h".
Thanks!
Patrick
Patrick Steinhardt (7):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix whitespace errors
t/clar: fix compatibility with NonStop
Makefile: wire up the clar unit testing framework
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 43 +-
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++-
t/unit-tests/unit-test.c | 17 +
t/unit-tests/unit-test.h | 3 +
29 files changed, 3160 insertions(+), 98 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v3:
1: 78a9cc1162 = 1: 086dd728a7 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: b6c066ee4e = 2: 5c22e0b3b9 t: import the clar unit testing framework
3: 35682b7686 ! 3: e0f99874cc t/clar: fix whitespace errors
@@ Commit message
Signed-off-by: Patrick Steinhardt <ps@pks.im>
+ ## t/unit-tests/clar/clar/fs.h ##
+@@ t/unit-tests/clar/clar/fs.h: fs_rm_wait(WCHAR *_wpath)
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+- Sleep(RM_RETRY_DELAY * retries * retries);
++ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+
## t/unit-tests/clar/clar/sandbox.h ##
@@ t/unit-tests/clar/clar/sandbox.h: find_tmp_path(char *buffer, size_t length)
static const size_t var_count = 5;
4: 7a76c21bcb = 4: 75e097dfa4 t/clar: fix compatibility with NonStop
5: 68b3c65951 ! 5: 5b8a64ae79 Makefile: wire up the clar unit testing framework
@@ Makefile: endif
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
+@@ Makefile: CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(LIB_H))
+ HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
+ HCC = $(HCO:hco=hcc)
+
++$(UNIT_TEST_DIR)/unit-test.hcc: $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar-decls.h
+ %.hcc: %.h
+ @echo '#include "git-compat-util.h"' >$@
+ @echo '#include "$<"' >>$@
@@ Makefile: endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
6: 4a0888380e = 6: bc4e23d666 t/unit-tests: convert strvec tests to use clar
7: f423b01c05 = 7: 0a7fe8775a t/unit-tests: convert ctype tests to use clar
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v4 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 2/7] t: import the clar unit testing framework Patrick Steinhardt
` (6 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06f..d2212de0b7 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b..63328ac630 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v4 2/7] t: import the clar unit testing framework
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 3/7] t/clar: fix whitespace errors Patrick Steinhardt
` (5 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Import the clar unit testing framework at commit faa8419 (Merge pull
request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 154 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 267 +++++++
t/unit-tests/clar/test/.gitignore | 5 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2946 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb1..5a432b7b29 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index 3863e60b66..674b0ac4e1 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3260,7 +3262,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 0000000000..b1ac2de460
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 0000000000..8983817f0c
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 0000000000..a8961c5f10
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 0000000000..3fc2c76815
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 0000000000..8c22382bd5
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 0000000000..6ec6423484
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 0000000000..a6eda5e5dc
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 0000000000..c17e2f693b
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 0000000000..0ba1479620
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,154 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
+
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 0000000000..4dd352e28b
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 0000000000..931b4d60d9
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
+
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 0000000000..3e8ae0a3a5
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 0000000000..93c6b2ad32
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 0000000000..0fcaa639aa
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 0000000000..59e56ad255
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 0000000000..a4d91b72fa
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 0000000000..220f4aa98a
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 0000000000..faa1209262
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v4 3/7] t/clar: fix whitespace errors
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 1/7] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 2/7] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 4/7] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (4 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Fix whitespace errors in the clar that make git-apply(1) unhappy. This
has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/97
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/fs.h | 2 +-
t/unit-tests/clar/clar/sandbox.h | 5 ++---
t/unit-tests/clar/generate.py | 1 -
t/unit-tests/clar/test/.gitignore | 1 -
4 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
index a6eda5e5dc..3e39890bd3 100644
--- a/t/unit-tests/clar/clar/fs.h
+++ b/t/unit-tests/clar/clar/fs.h
@@ -146,7 +146,7 @@ fs_rm_wait(WCHAR *_wpath)
ERROR_PATH_NOT_FOUND == last_error)
return 0;
- Sleep(RM_RETRY_DELAY * retries * retries);
+ Sleep(RM_RETRY_DELAY * retries * retries);
}
while (retries++ <= RM_RETRY_COUNT);
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..7c177f3525 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -25,9 +25,9 @@ find_tmp_path(char *buffer, size_t length)
static const size_t var_count = 5;
static const char *env_vars[] = {
"CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
- };
+ };
- size_t i;
+ size_t i;
for (i = 0; i < var_count; ++i) {
const char *env = getenv(env_vars[i]);
@@ -151,4 +151,3 @@ const char *clar_sandbox_path(void)
{
return _clar_path;
}
-
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
index 931b4d60d9..80996ac3e7 100755
--- a/t/unit-tests/clar/generate.py
+++ b/t/unit-tests/clar/generate.py
@@ -264,4 +264,3 @@ def write(self):
suite.disable(options.excluded)
if suite.write():
print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
-
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
index 3e8ae0a3a5..a477d0c40c 100644
--- a/t/unit-tests/clar/test/.gitignore
+++ b/t/unit-tests/clar/test/.gitignore
@@ -2,4 +2,3 @@ clar.suite
.clarcache
clar_test
*.o
-
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v4 4/7] t/clar: fix compatibility with NonStop
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
` (2 preceding siblings ...)
2024-08-15 9:47 ` [PATCH v4 3/7] t/clar: fix whitespace errors Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 5/7] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (3 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f3525..e25057b7c4 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v4 5/7] Makefile: wire up the clar unit testing framework
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
` (3 preceding siblings ...)
2024-08-15 9:47 ` [PATCH v4 4/7] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (2 subsequent siblings)
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 35 +++++++++++++++++++++---
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 18 ++++++++++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 106 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c2..6687bd6db4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 674b0ac4e1..1311bbbbed 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,6 +1334,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2713,6 +2718,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3215,7 +3221,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3279,6 +3285,7 @@ CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(LIB_H))
HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
HCC = $(HCO:hco=hcc)
+$(UNIT_TEST_DIR)/unit-test.hcc: $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar-decls.h
%.hcc: %.h
@echo '#include "git-compat-util.h"' >$@
@echo '#include "$<"' >>$@
@@ -3646,7 +3653,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3702,6 +3709,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3861,7 +3869,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b7..131ffd778f 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec..d0632ec7f9 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 0000000000..ab71ce6c9f
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 0000000000..32a81299e9
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,18 @@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **argv_copy;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+ ALLOC_ARRAY(argv_copy, argc + 2);
+ COPY_ARRAY(argv_copy, argv, argc);
+ argv_copy[argc++] = "-t";
+ argv_copy[argc] = NULL;
+
+ ret = clar_test(argc, (char **) argv_copy);
+
+ free(argv_copy);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 0000000000..66ec2387cc
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v4 6/7] t/unit-tests: convert strvec tests to use clar
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
` (4 preceding siblings ...)
2024-08-15 9:47 ` [PATCH v4 5/7] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 9:47 ` [PATCH v4 7/7] t/unit-tests: convert ctype " Patrick Steinhardt
2024-08-15 16:21 ` [PATCH v4 0/7] Introduce clar testing framework Junio C Hamano
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++++++++++----------------
t/unit-tests/unit-test.c | 3 +-
3 files changed, 45 insertions(+), 79 deletions(-)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
diff --git a/Makefile b/Makefile
index 1311bbbbed..ebc12b2bf1 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,6 +1334,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1351,7 +1352,6 @@ UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
similarity index 54%
rename from t/unit-tests/t-strvec.c
rename to t/unit-tests/strvec.c
index fa1a041469..d11ed0f28d 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/strvec.c
@@ -1,52 +1,46 @@
-#include "test-lib.h"
+#include "unit-test.h"
#include "strbuf.h"
#include "strvec.h"
#define check_strvec(vec, ...) \
do { \
const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
} while (0)
-static void t_static_init(void)
+void test_strvec__init(void)
{
struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_dynamic_init(void)
+void test_strvec__dynamic_init(void)
{
struct strvec vec;
strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_clear(void)
+void test_strvec__clear(void)
{
struct strvec vec = STRVEC_INIT;
strvec_push(&vec, "foo");
strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_push(void)
+void test_strvec__push(void)
{
struct strvec vec = STRVEC_INIT;
@@ -59,7 +53,7 @@ static void t_push(void)
strvec_clear(&vec);
}
-static void t_pushf(void)
+void test_strvec__pushf(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushf(&vec, "foo: %d", 1);
@@ -67,7 +61,7 @@ static void t_pushf(void)
strvec_clear(&vec);
}
-static void t_pushl(void)
+void test_strvec__pushl(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -75,7 +69,7 @@ static void t_pushl(void)
strvec_clear(&vec);
}
-static void t_pushv(void)
+void test_strvec__pushv(void)
{
const char *strings[] = {
"foo", "bar", "baz", NULL,
@@ -88,7 +82,7 @@ static void t_pushv(void)
strvec_clear(&vec);
}
-static void t_replace_at_head(void)
+void test_strvec__replace_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -97,7 +91,7 @@ static void t_replace_at_head(void)
strvec_clear(&vec);
}
-static void t_replace_at_tail(void)
+void test_strvec__replace_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -106,7 +100,7 @@ static void t_replace_at_tail(void)
strvec_clear(&vec);
}
-static void t_replace_in_between(void)
+void test_strvec__replace_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -115,7 +109,7 @@ static void t_replace_in_between(void)
strvec_clear(&vec);
}
-static void t_replace_with_substring(void)
+void test_strvec__replace_with_substring(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", NULL);
@@ -124,7 +118,7 @@ static void t_replace_with_substring(void)
strvec_clear(&vec);
}
-static void t_remove_at_head(void)
+void test_strvec__remove_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -133,7 +127,7 @@ static void t_remove_at_head(void)
strvec_clear(&vec);
}
-static void t_remove_at_tail(void)
+void test_strvec__remove_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -142,7 +136,7 @@ static void t_remove_at_tail(void)
strvec_clear(&vec);
}
-static void t_remove_in_between(void)
+void test_strvec__remove_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -151,7 +145,7 @@ static void t_remove_in_between(void)
strvec_clear(&vec);
}
-static void t_pop_empty_array(void)
+void test_strvec__pop_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pop(&vec);
@@ -159,7 +153,7 @@ static void t_pop_empty_array(void)
strvec_clear(&vec);
}
-static void t_pop_non_empty_array(void)
+void test_strvec__pop_non_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -168,7 +162,7 @@ static void t_pop_non_empty_array(void)
strvec_clear(&vec);
}
-static void t_split_empty_string(void)
+void test_strvec__split_empty_string(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "");
@@ -176,7 +170,7 @@ static void t_split_empty_string(void)
strvec_clear(&vec);
}
-static void t_split_single_item(void)
+void test_strvec__split_single_item(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo");
@@ -184,7 +178,7 @@ static void t_split_single_item(void)
strvec_clear(&vec);
}
-static void t_split_multiple_items(void)
+void test_strvec__split_multiple_items(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo bar baz");
@@ -192,7 +186,7 @@ static void t_split_multiple_items(void)
strvec_clear(&vec);
}
-static void t_split_whitespace_only(void)
+void test_strvec__split_whitespace_only(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, " \t\n");
@@ -200,7 +194,7 @@ static void t_split_whitespace_only(void)
strvec_clear(&vec);
}
-static void t_split_multiple_consecutive_whitespaces(void)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo\n\t bar");
@@ -208,7 +202,7 @@ static void t_split_multiple_consecutive_whitespaces(void)
strvec_clear(&vec);
}
-static void t_detach(void)
+void test_strvec__detach(void)
{
struct strvec vec = STRVEC_INIT;
const char **detached;
@@ -216,40 +210,13 @@ static void t_detach(void)
strvec_push(&vec, "foo");
detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
free((char *) detached[0]);
free(detached);
}
-
-int cmd_main(int argc, const char **argv)
-{
- TEST(t_static_init(), "static initialization");
- TEST(t_dynamic_init(), "dynamic initialization");
- TEST(t_clear(), "clear");
- TEST(t_push(), "push");
- TEST(t_pushf(), "pushf");
- TEST(t_pushl(), "pushl");
- TEST(t_pushv(), "pushv");
- TEST(t_replace_at_head(), "replace at head");
- TEST(t_replace_in_between(), "replace in between");
- TEST(t_replace_at_tail(), "replace at tail");
- TEST(t_replace_with_substring(), "replace with substring");
- TEST(t_remove_at_head(), "remove at head");
- TEST(t_remove_in_between(), "remove in between");
- TEST(t_remove_at_tail(), "remove at tail");
- TEST(t_pop_empty_array(), "pop with empty array");
- TEST(t_pop_non_empty_array(), "pop with non-empty array");
- TEST(t_split_empty_string(), "split empty string");
- TEST(t_split_single_item(), "split single item");
- TEST(t_split_multiple_items(), "split multiple items");
- TEST(t_split_whitespace_only(), "split whitespace only");
- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
- TEST(t_detach(), "detach");
- return test_done();
-}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 32a81299e9..82b7635e6a 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
int ret;
/* Append the "-t" flag such that the tests generate TAP output. */
- ALLOC_ARRAY(argv_copy, argc + 2);
+ ALLOC_ARRAY(argv_copy, argc + 1);
COPY_ARRAY(argv_copy, argv, argc);
argv_copy[argc++] = "-t";
- argv_copy[argc] = NULL;
ret = clar_test(argc, (char **) argv_copy);
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v4 7/7] t/unit-tests: convert ctype tests to use clar
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
` (5 preceding siblings ...)
2024-08-15 9:47 ` [PATCH v4 6/7] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-15 9:47 ` Patrick Steinhardt
2024-08-15 16:21 ` [PATCH v4 0/7] Introduce clar testing framework Junio C Hamano
7 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-15 9:47 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Convert the ctype tests to use the new clar unit testing framework.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------
2 files changed, 59 insertions(+), 14 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
diff --git a/Makefile b/Makefile
index ebc12b2bf1..d28faabbf4 100644
--- a/Makefile
+++ b/Makefile
@@ -1334,13 +1334,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-mem-pool
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 71%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index d6ac1fe678..311df3a539 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,18 +1,12 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- int skip = test__run_begin(); \
- if (!skip) { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ for (int i = 0; i < 256; i++) \
+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -33,21 +27,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v4 0/7] Introduce clar testing framework
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
` (6 preceding siblings ...)
2024-08-15 9:47 ` [PATCH v4 7/7] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-08-15 16:21 ` Junio C Hamano
2024-08-16 5:10 ` Patrick Steinhardt
7 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-15 16:21 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Patrick Steinhardt <ps@pks.im> writes:
> Hi,
>
> this is the fourth version of my patch series that introduces the clar
> testing framework for our unit tests.
>
> Changes compared to v3:
>
> - Seeing that the overall feedback seems to be rather positive, I've
> now dropped the "RFC" prefix.
>
> - Fix another whitespace-damaged line in the clar unit test. I've
> adapted the upstream pull request accordingly.
>
> - Fix `make hdr-check`, which failed due to the missing dependency on
> "clar-decls.h".
Similarly, lack of clar.suite still makes "make sparse" fail, it
seems.
$ make NO_REGEX=NoThanks -j32 sparse
...
SP builtin/help.c
CC t/unit-tests/ctype.o
CC t/unit-tests/strvec.o
CC t/unit-tests/clar/clar.o
CC t/unit-tests/unit-test.o
t/unit-tests/clar/clar.c:209:10: fatal error: clar.suite: No such file or directory
209 | #include "clar.suite"
| ^~~~~~~~~~~~
compilation terminated.
make: *** [Makefile:2762: t/unit-tests/clar/clar.o] Error 1
make: *** Waiting for unfinished jobs....
The NO_REGEX is there to force us to go check the compat/regex.h as well.
> +@@ Makefile: CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(LIB_H))
> + HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
> + HCC = $(HCO:hco=hcc)
> +
> ++$(UNIT_TEST_DIR)/unit-test.hcc: $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar-decls.h
> + %.hcc: %.h
> + @echo '#include "git-compat-util.h"' >$@
> + @echo '#include "$<"' >>$@
This is a nice addition.
Thanks.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v4 0/7] Introduce clar testing framework
2024-08-15 16:21 ` [PATCH v4 0/7] Introduce clar testing framework Junio C Hamano
@ 2024-08-16 5:10 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 5:10 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
On Thu, Aug 15, 2024 at 09:21:04AM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > Hi,
> >
> > this is the fourth version of my patch series that introduces the clar
> > testing framework for our unit tests.
> >
> > Changes compared to v3:
> >
> > - Seeing that the overall feedback seems to be rather positive, I've
> > now dropped the "RFC" prefix.
> >
> > - Fix another whitespace-damaged line in the clar unit test. I've
> > adapted the upstream pull request accordingly.
> >
> > - Fix `make hdr-check`, which failed due to the missing dependency on
> > "clar-decls.h".
>
> Similarly, lack of clar.suite still makes "make sparse" fail, it
> seems.
>
> $ make NO_REGEX=NoThanks -j32 sparse
> ...
> SP builtin/help.c
> CC t/unit-tests/ctype.o
> CC t/unit-tests/strvec.o
> CC t/unit-tests/clar/clar.o
> CC t/unit-tests/unit-test.o
> t/unit-tests/clar/clar.c:209:10: fatal error: clar.suite: No such file or directory
> 209 | #include "clar.suite"
> | ^~~~~~~~~~~~
> compilation terminated.
> make: *** [Makefile:2762: t/unit-tests/clar/clar.o] Error 1
> make: *** Waiting for unfinished jobs....
>
> The NO_REGEX is there to force us to go check the compat/regex.h as well.
Huh, OK. Wonder why the CI didn't catch this. I'll have another look,
thanks.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v5 0/9] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (7 preceding siblings ...)
2024-08-15 9:47 ` [PATCH v4 " Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 1/9] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (9 more replies)
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (2 subsequent siblings)
11 siblings, 10 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Hi,
this is the fifth version of my patch series that introduces the clar
testing framework for our unit tests.
Changes compared to v4:
- The whitespace fixes have been merged upstream, so I've updated the
embedded copy of clar and dropped the subsequent patch that fixed
them in our copy. The NonStop compatibility fixes have not yet been
merged as the pull request needs some more work.
- Both "clar-decls.h" and "clar.suite" are now part of GENERATED_H.
This brings removal of these files via "make clean" for free.
- The "sparse" target already depends on GENERATED_H, but in a broken
way. I've fixed that in a new commit.
- The "sparse" target no longer checks external sources, including the
clar sources.
- The "hdr-check" target now depends on GENERATED_H, as well. This
avoids having to manually wire up dependencies on generated headers
per file, which seems rather unmaintainable to me.
With this, the "hdr-check" and "sparse" targets all work on my machine
now.
Thanks!
Patrick
Patrick Steinhardt (9):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++-
t/unit-tests/unit-test.c | 17 +
t/unit-tests/unit-test.h | 3 +
29 files changed, 3166 insertions(+), 102 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v4:
1: 086dd728a7 ! 1: 832dc0496f t: do not pass GIT_TEST_OPTS to unit tests with prove
@@ Commit message
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
+ Signed-off-by: Patrick Steinhardt <ps@pks.im>
+
## t/Makefile ##
@@ t/Makefile: failed:
test -z "$$failed" || $(MAKE) $$failed
2: 5c22e0b3b9 ! 2: 3690607933 t: import the clar unit testing framework
@@ Metadata
## Commit message ##
t: import the clar unit testing framework
- Import the clar unit testing framework at commit faa8419 (Merge pull
- request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
+ Import the clar unit testing framework at commit 1516124 (Merge pull
+ request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
@@ t/unit-tests/clar/clar/fs.h (new)
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
-+ Sleep(RM_RETRY_DELAY * retries * retries);
++ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
@@ t/unit-tests/clar/clar/sandbox.h (new)
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
-+ };
++ };
+
-+ size_t i;
++ size_t i;
+
+ for (i = 0; i < var_count; ++i) {
+ const char *env = getenv(env_vars[i]);
@@ t/unit-tests/clar/clar/sandbox.h (new)
+{
+ return _clar_path;
+}
-+
## t/unit-tests/clar/clar/summary.h (new) ##
@@
@@ t/unit-tests/clar/generate.py (new)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
-+
## t/unit-tests/clar/test/.gitignore (new) ##
@@
@@ t/unit-tests/clar/test/.gitignore (new)
+.clarcache
+clar_test
+*.o
-+
## t/unit-tests/clar/test/Makefile (new) ##
@@
4: 75e097dfa4 = 3: db53673294 t/clar: fix compatibility with NonStop
3: e0f99874cc ! 4: b6199c88dd t/clar: fix whitespace errors
@@ Metadata
Author: Patrick Steinhardt <ps@pks.im>
## Commit message ##
- t/clar: fix whitespace errors
-
- Fix whitespace errors in the clar that make git-apply(1) unhappy. This
- has been cherry-picked from the upstream pull request at [1].
-
- [1]: https://github.com/clar-test/clar/pull/97
+ Makefile: fix sparse dependency on GENERATED_H
+
+ The "check" Makefile target is essentially an alias around the "sparse"
+ target. The one difference though is that it will tell users to instead
+ run the "test" target in case they do not have sparse(1) installed, as
+ chances are high that they wanted to execute the test suite rather than
+ doing semantic checks.
+
+ But even though the "check" target ultimately just ends up executing
+ `make sparse`, it still depends on our generated headers. This does not
+ make any sense though: they are irrelevant for the "test" target advice,
+ and if these headers are required for the "sparse" target they must be
+ declared as a dependency on the aliased target, not the alias.
+
+ But even moving the dependency to the "sparse" target is wrong, as
+ concurrent builds may then end up generating the headers and running
+ sparse concurrently. Instead, we make them a dependency of the specific
+ objects. While that is overly broad, it does ensure correct ordering.
+ The alternative, specifying which file depends on what generated header
+ explicitly, feels rather unmaintainable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
- ## t/unit-tests/clar/clar/fs.h ##
-@@ t/unit-tests/clar/clar/fs.h: fs_rm_wait(WCHAR *_wpath)
- ERROR_PATH_NOT_FOUND == last_error)
- return 0;
-
-- Sleep(RM_RETRY_DELAY * retries * retries);
-+ Sleep(RM_RETRY_DELAY * retries * retries);
- }
- while (retries++ <= RM_RETRY_COUNT);
+ ## Makefile ##
+@@ Makefile: check-sha1:: t/helper/test-tool$X
-
- ## t/unit-tests/clar/clar/sandbox.h ##
-@@ t/unit-tests/clar/clar/sandbox.h: find_tmp_path(char *buffer, size_t length)
- static const size_t var_count = 5;
- static const char *env_vars[] = {
- "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
-- };
-+ };
+ SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
-- size_t i;
-+ size_t i;
+-$(SP_OBJ): %.sp: %.c %.o
++$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
+ $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
+ -Wsparse-error \
+ $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
+@@ Makefile: style:
+ git clang-format --style file --diff --extensions c,h
- for (i = 0; i < var_count; ++i) {
- const char *env = getenv(env_vars[i]);
-@@ t/unit-tests/clar/clar/sandbox.h: const char *clar_sandbox_path(void)
- {
- return _clar_path;
- }
--
-
- ## t/unit-tests/clar/generate.py ##
-@@ t/unit-tests/clar/generate.py: def write(self):
- suite.disable(options.excluded)
- if suite.write():
- print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
--
-
- ## t/unit-tests/clar/test/.gitignore ##
-@@ t/unit-tests/clar/test/.gitignore: clar.suite
- .clarcache
- clar_test
- *.o
--
+ .PHONY: check
+-check: $(GENERATED_H)
++check:
+ @if sparse; \
+ then \
+ echo >&2 "Use 'make sparse' instead"; \
-: ---------- > 5: 06364b2b72 Makefile: make hdr-check depend on generated headers
-: ---------- > 6: 88ea94ce16 Makefile: do not use sparse on third-party sources
5: 5b8a64ae79 ! 7: 05bcb5bef6 Makefile: wire up the clar unit testing framework
@@ .gitignore
/bin-wrappers/
## Makefile ##
+@@ Makefile: REFTABLE_TEST_LIB = reftable/libreftable_test.a
+ GENERATED_H += command-list.h
+ GENERATED_H += config-list.h
+ GENERATED_H += hook-list.h
++GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h
++GENERATED_H += $(UNIT_TEST_DIR)/clar.suite
+
+ .PHONY: generated-hdrs
+ generated-hdrs: $(GENERATED_H)
@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
@@ Makefile: endif
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
-@@ Makefile: CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(LIB_H))
- HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
- HCC = $(HCO:hco=hcc)
-
-+$(UNIT_TEST_DIR)/unit-test.hcc: $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar-decls.h
- %.hcc: %.h
- @echo '#include "git-compat-util.h"' >$@
- @echo '#include "$<"' >>$@
@@ Makefile: endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
@@ Makefile: cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
-+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
++ $(RM) GIT-TEST-SUITES
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
6: bc4e23d666 = 8: 8f56b4d626 t/unit-tests: convert strvec tests to use clar
7: 0a7fe8775a = 9: ca09d19fd5 t/unit-tests: convert ctype tests to use clar
base-commit: 406f326d271e0bacecdb00425422c5fa3f314930
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v5 1/9] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 2/9] t: import the clar unit testing framework Patrick Steinhardt
` (8 subsequent siblings)
9 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06f..d2212de0b7 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b..63328ac630 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 1/9] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 13:37 ` Phillip Wood
2024-08-19 21:21 ` Junio C Hamano
2024-08-16 7:04 ` [PATCH v5 3/9] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (7 subsequent siblings)
9 siblings, 2 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Import the clar unit testing framework at commit 1516124 (Merge pull
request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 153 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2943 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb1..5a432b7b29 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index 3863e60b66..674b0ac4e1 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3260,7 +3262,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 0000000000..b1ac2de460
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 0000000000..8983817f0c
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 0000000000..a8961c5f10
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 0000000000..3fc2c76815
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 0000000000..8c22382bd5
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 0000000000..6ec6423484
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 0000000000..3e39890bd3
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 0000000000..c17e2f693b
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 0000000000..7c177f3525
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,153 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 0000000000..4dd352e28b
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 0000000000..80996ac3e7
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 0000000000..a477d0c40c
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 0000000000..93c6b2ad32
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 0000000000..0fcaa639aa
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 0000000000..59e56ad255
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 0000000000..a4d91b72fa
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 0000000000..220f4aa98a
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 0000000000..faa1209262
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-16 7:04 ` [PATCH v5 2/9] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-16 13:37 ` Phillip Wood
2024-08-23 12:16 ` Johannes Schindelin
2024-08-19 21:21 ` Junio C Hamano
1 sibling, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-16 13:37 UTC (permalink / raw)
To: Patrick Steinhardt, git, Johannes Schindelin
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Johannes - are these format specifiers supported by the version of
MSVCRT that is used by git for windows? c.f.
https://lore.kernel.org/git/nycvar.QRO.7.76.6.2203241456250.388@tvgsbejvaqbjf.bet/
On 16/08/2024 08:04, Patrick Steinhardt wrote:
> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
> [...]
> +#ifdef _WIN32
> [...]
> +# ifndef PRIuZ
> +# define PRIuZ "Iu"
> +# endif
> +# ifndef PRIxZ
> +# define PRIxZ "Ix"
> +# endif
Thanks
Phillip
> +# if defined(_MSC_VER) || defined(__MINGW32__)
> + typedef struct stat STAT_T;
> +# else
> + typedef struct _stat STAT_T;
> +# endif
> +#else
> +# include <sys/wait.h> /* waitpid(2) */
> +# include <unistd.h>
> +# define _MAIN_CC
> +# define p_snprintf snprintf
> +# ifndef PRIuZ
> +# define PRIuZ "zu"
> +# endif
> +# ifndef PRIxZ
> +# define PRIxZ "zx"
> +# endif
> + typedef struct stat STAT_T;
> +#endif
> +
> +#define MAX(x, y) (((x) > (y)) ? (x) : (y))
> +
> +#include "clar.h"
> +
> +static void fs_rm(const char *_source);
> +static void fs_copy(const char *_source, const char *dest);
> +
> +#ifdef CLAR_FIXTURE_PATH
> +static const char *
> +fixture_path(const char *base, const char *fixture_name);
> +#endif
> +
> +struct clar_error {
> + const char *file;
> + const char *function;
> + size_t line_number;
> + const char *error_msg;
> + char *description;
> +
> + struct clar_error *next;
> +};
> +
> +struct clar_explicit {
> + size_t suite_idx;
> + const char *filter;
> +
> + struct clar_explicit *next;
> +};
> +
> +struct clar_report {
> + const char *test;
> + int test_number;
> + const char *suite;
> +
> + enum cl_test_status status;
> + time_t start;
> + double elapsed;
> +
> + struct clar_error *errors;
> + struct clar_error *last_error;
> +
> + struct clar_report *next;
> +};
> +
> +struct clar_summary {
> + const char *filename;
> + FILE *fp;
> +};
> +
> +static struct {
> + enum cl_test_status test_status;
> +
> + const char *active_test;
> + const char *active_suite;
> +
> + int total_skipped;
> + int total_errors;
> +
> + int tests_ran;
> + int suites_ran;
> +
> + enum cl_output_format output_format;
> +
> + int report_errors_only;
> + int exit_on_error;
> + int verbosity;
> +
> + int write_summary;
> + char *summary_filename;
> + struct clar_summary *summary;
> +
> + struct clar_explicit *explicit;
> + struct clar_explicit *last_explicit;
> +
> + struct clar_report *reports;
> + struct clar_report *last_report;
> +
> + void (*local_cleanup)(void *);
> + void *local_cleanup_payload;
> +
> + jmp_buf trampoline;
> + int trampoline_enabled;
> +
> + cl_trace_cb *pfn_trace_cb;
> + void *trace_payload;
> +
> +} _clar;
> +
> +struct clar_func {
> + const char *name;
> + void (*ptr)(void);
> +};
> +
> +struct clar_suite {
> + const char *name;
> + struct clar_func initialize;
> + struct clar_func cleanup;
> + const struct clar_func *tests;
> + size_t test_count;
> + int enabled;
> +};
> +
> +/* From clar_print_*.c */
> +static void clar_print_init(int test_count, int suite_count, const char *suite_names);
> +static void clar_print_shutdown(int test_count, int suite_count, int error_count);
> +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
> +static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
> +static void clar_print_onsuite(const char *suite_name, int suite_index);
> +static void clar_print_onabort(const char *msg, ...);
> +
> +/* From clar_sandbox.c */
> +static void clar_unsandbox(void);
> +static int clar_sandbox(void);
> +
> +/* From summary.h */
> +static struct clar_summary *clar_summary_init(const char *filename);
> +static int clar_summary_shutdown(struct clar_summary *fp);
> +
> +/* Load the declarations for the test suite */
> +#include "clar.suite"
> +
> +
> +#define CL_TRACE(ev) \
> + do { \
> + if (_clar.pfn_trace_cb) \
> + _clar.pfn_trace_cb(ev, \
> + _clar.active_suite, \
> + _clar.active_test, \
> + _clar.trace_payload); \
> + } while (0)
> +
> +void cl_trace_register(cl_trace_cb *cb, void *payload)
> +{
> + _clar.pfn_trace_cb = cb;
> + _clar.trace_payload = payload;
> +}
> +
> +
> +/* Core test functions */
> +static void
> +clar_report_errors(struct clar_report *report)
> +{
> + struct clar_error *error;
> + int i = 1;
> +
> + for (error = report->errors; error; error = error->next)
> + clar_print_error(i++, _clar.last_report, error);
> +}
> +
> +static void
> +clar_report_all(void)
> +{
> + struct clar_report *report;
> + struct clar_error *error;
> + int i = 1;
> +
> + for (report = _clar.reports; report; report = report->next) {
> + if (report->status != CL_TEST_FAILURE)
> + continue;
> +
> + for (error = report->errors; error; error = error->next)
> + clar_print_error(i++, report, error);
> + }
> +}
> +
> +#ifdef WIN32
> +# define clar_time DWORD
> +
> +static void clar_time_now(clar_time *out)
> +{
> + *out = GetTickCount();
> +}
> +
> +static double clar_time_diff(clar_time *start, clar_time *end)
> +{
> + return ((double)*end - (double)*start) / 1000;
> +}
> +#else
> +# include <sys/time.h>
> +
> +# define clar_time struct timeval
> +
> +static void clar_time_now(clar_time *out)
> +{
> + struct timezone tz;
> +
> + gettimeofday(out, &tz);
> +}
> +
> +static double clar_time_diff(clar_time *start, clar_time *end)
> +{
> + return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
> + ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
> +}
> +#endif
> +
> +static void
> +clar_run_test(
> + const struct clar_suite *suite,
> + const struct clar_func *test,
> + const struct clar_func *initialize,
> + const struct clar_func *cleanup)
> +{
> + clar_time start, end;
> +
> + _clar.trampoline_enabled = 1;
> +
> + CL_TRACE(CL_TRACE__TEST__BEGIN);
> +
> + _clar.last_report->start = time(NULL);
> + clar_time_now(&start);
> +
> + if (setjmp(_clar.trampoline) == 0) {
> + if (initialize->ptr != NULL)
> + initialize->ptr();
> +
> + CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
> + test->ptr();
> + CL_TRACE(CL_TRACE__TEST__RUN_END);
> + }
> +
> + clar_time_now(&end);
> +
> + _clar.trampoline_enabled = 0;
> +
> + if (_clar.last_report->status == CL_TEST_NOTRUN)
> + _clar.last_report->status = CL_TEST_OK;
> +
> + _clar.last_report->elapsed = clar_time_diff(&start, &end);
> +
> + if (_clar.local_cleanup != NULL)
> + _clar.local_cleanup(_clar.local_cleanup_payload);
> +
> + if (cleanup->ptr != NULL)
> + cleanup->ptr();
> +
> + CL_TRACE(CL_TRACE__TEST__END);
> +
> + _clar.tests_ran++;
> +
> + /* remove any local-set cleanup methods */
> + _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);
> + }
> +}
> +
> +static void
> +clar_run_suite(const struct clar_suite *suite, const char *filter)
> +{
> + const struct clar_func *test = suite->tests;
> + size_t i, matchlen;
> + struct clar_report *report;
> + int exact = 0;
> +
> + if (!suite->enabled)
> + return;
> +
> + if (_clar.exit_on_error && _clar.total_errors)
> + return;
> +
> + if (!_clar.report_errors_only)
> + clar_print_onsuite(suite->name, ++_clar.suites_ran);
> +
> + _clar.active_suite = suite->name;
> + _clar.active_test = NULL;
> + CL_TRACE(CL_TRACE__SUITE_BEGIN);
> +
> + if (filter) {
> + size_t suitelen = strlen(suite->name);
> + matchlen = strlen(filter);
> + if (matchlen <= suitelen) {
> + filter = NULL;
> + } else {
> + filter += suitelen;
> + while (*filter == ':')
> + ++filter;
> + matchlen = strlen(filter);
> +
> + if (matchlen && filter[matchlen - 1] == '$') {
> + exact = 1;
> + matchlen--;
> + }
> + }
> + }
> +
> + for (i = 0; i < suite->test_count; ++i) {
> + if (filter && strncmp(test[i].name, filter, matchlen))
> + continue;
> +
> + if (exact && strlen(test[i].name) != matchlen)
> + continue;
> +
> + _clar.active_test = test[i].name;
> +
> + report = calloc(1, sizeof(struct clar_report));
> + report->suite = _clar.active_suite;
> + report->test = _clar.active_test;
> + report->test_number = _clar.tests_ran;
> + report->status = CL_TEST_NOTRUN;
> +
> + if (_clar.reports == NULL)
> + _clar.reports = report;
> +
> + if (_clar.last_report != NULL)
> + _clar.last_report->next = report;
> +
> + _clar.last_report = report;
> +
> + clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
> +
> + if (_clar.exit_on_error && _clar.total_errors)
> + return;
> + }
> +
> + _clar.active_test = NULL;
> + CL_TRACE(CL_TRACE__SUITE_END);
> +}
> +
> +static void
> +clar_usage(const char *arg)
> +{
> + printf("Usage: %s [options]\n\n", arg);
> + printf("Options:\n");
> + printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
> + 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 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);
> +}
> +
> +static void
> +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) {
> + clar_usage(argv[0]);
> + }
> + }
> +
> + for (i = 1; i < argc; ++i) {
> + char *argument = argv[i];
> +
> + switch (argument[1]) {
> + case 's':
> + case 'i':
> + case 'x': { /* given suite name */
> + int offset = (argument[2] == '=') ? 3 : 2, found = 0;
> + char action = argument[1];
> + size_t j, arglen, suitelen, cmplen;
> +
> + argument += offset;
> + arglen = strlen(argument);
> +
> + if (arglen == 0)
> + clar_usage(argv[0]);
> +
> + for (j = 0; j < _clar_suite_count; ++j) {
> + suitelen = strlen(_clar_suites[j].name);
> + cmplen = (arglen < suitelen) ? arglen : suitelen;
> +
> + if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
> + int exact = (arglen >= suitelen);
> +
> + /* Do we have a real suite prefix separated by a
> + * trailing '::' or just a matching substring? */
> + if (arglen > suitelen && (argument[suitelen] != ':'
> + || argument[suitelen + 1] != ':'))
> + continue;
> +
> + ++found;
> +
> + if (!exact)
> + _clar.verbosity = MAX(_clar.verbosity, 1);
> +
> + switch (action) {
> + case 's': {
> + struct clar_explicit *explicit =
> + calloc(1, sizeof(struct clar_explicit));
> + assert(explicit);
> +
> + explicit->suite_idx = j;
> + explicit->filter = argument;
> +
> + if (_clar.explicit == NULL)
> + _clar.explicit = explicit;
> +
> + if (_clar.last_explicit != NULL)
> + _clar.last_explicit->next = explicit;
> +
> + _clar_suites[j].enabled = 1;
> + _clar.last_explicit = explicit;
> + break;
> + }
> + case 'i': _clar_suites[j].enabled = 1; break;
> + case 'x': _clar_suites[j].enabled = 0; break;
> + }
> +
> + if (exact)
> + break;
> + }
> + }
> +
> + if (!found) {
> + clar_print_onabort("No suite matching '%s' found.\n", argument);
> + exit(-1);
> + }
> + break;
> + }
> +
> + case 'q':
> + _clar.report_errors_only = 1;
> + break;
> +
> + case 'Q':
> + _clar.exit_on_error = 1;
> + break;
> +
> + case 't':
> + _clar.output_format = CL_OUTPUT_TAP;
> + break;
> +
> + case 'l': {
> + size_t j;
> + 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);
> +
> + exit(0);
> + }
> +
> + case 'v':
> + _clar.verbosity++;
> + break;
> +
> + case 'r':
> + _clar.write_summary = 1;
> + free(_clar.summary_filename);
> + _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
> + break;
> +
> + default:
> + assert(!"Unexpected commandline argument!");
> + }
> + }
> +}
> +
> +void
> +clar_test_init(int argc, char **argv)
> +{
> + const char *summary_env;
> +
> + if (argc > 1)
> + clar_parse_args(argc, argv);
> +
> + clar_print_init(
> + (int)_clar_callback_count,
> + (int)_clar_suite_count,
> + ""
> + );
> +
> + if (!_clar.summary_filename &&
> + (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
> + _clar.write_summary = 1;
> + _clar.summary_filename = strdup(summary_env);
> + }
> +
> + if (_clar.write_summary && !_clar.summary_filename)
> + _clar.summary_filename = strdup("summary.xml");
> +
> + if (_clar.write_summary &&
> + !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
> + clar_print_onabort("Failed to open the summary file\n");
> + exit(-1);
> + }
> +
> + if (clar_sandbox() < 0) {
> + clar_print_onabort("Failed to sandbox the test runner.\n");
> + exit(-1);
> + }
> +}
> +
> +int
> +clar_test_run(void)
> +{
> + size_t i;
> + struct clar_explicit *explicit;
> +
> + if (_clar.explicit) {
> + for (explicit = _clar.explicit; explicit; explicit = explicit->next)
> + clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
> + } else {
> + for (i = 0; i < _clar_suite_count; ++i)
> + clar_run_suite(&_clar_suites[i], NULL);
> + }
> +
> + return _clar.total_errors;
> +}
> +
> +void
> +clar_test_shutdown(void)
> +{
> + struct clar_explicit *explicit, *explicit_next;
> + struct clar_report *report, *report_next;
> +
> + clar_print_shutdown(
> + _clar.tests_ran,
> + (int)_clar_suite_count,
> + _clar.total_errors
> + );
> +
> + clar_unsandbox();
> +
> + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
> + clar_print_onabort("Failed to write the summary file\n");
> + exit(-1);
> + }
> +
> + for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
> + explicit_next = explicit->next;
> + free(explicit);
> + }
> +
> + for (report = _clar.reports; report; report = report_next) {
> + report_next = report->next;
> + free(report);
> + }
> +
> + free(_clar.summary_filename);
> +}
> +
> +int
> +clar_test(int argc, char **argv)
> +{
> + int errors;
> +
> + clar_test_init(argc, argv);
> + errors = clar_test_run();
> + clar_test_shutdown();
> +
> + return errors;
> +}
> +
> +static void abort_test(void)
> +{
> + if (!_clar.trampoline_enabled) {
> + clar_print_onabort(
> + "Fatal error: a cleanup method raised an exception.");
> + clar_report_errors(_clar.last_report);
> + exit(-1);
> + }
> +
> + CL_TRACE(CL_TRACE__TEST__LONGJMP);
> + longjmp(_clar.trampoline, -1);
> +}
> +
> +void clar__skip(void)
> +{
> + _clar.last_report->status = CL_TEST_SKIP;
> + _clar.total_skipped++;
> + abort_test();
> +}
> +
> +void clar__fail(
> + const char *file,
> + const char *function,
> + size_t line,
> + const char *error_msg,
> + const char *description,
> + int should_abort)
> +{
> + struct clar_error *error = calloc(1, sizeof(struct clar_error));
> +
> + if (_clar.last_report->errors == NULL)
> + _clar.last_report->errors = error;
> +
> + if (_clar.last_report->last_error != NULL)
> + _clar.last_report->last_error->next = error;
> +
> + _clar.last_report->last_error = error;
> +
> + error->file = file;
> + error->function = function;
> + error->line_number = line;
> + error->error_msg = error_msg;
> +
> + if (description != NULL)
> + error->description = strdup(description);
> +
> + _clar.total_errors++;
> + _clar.last_report->status = CL_TEST_FAILURE;
> +
> + if (should_abort)
> + abort_test();
> +}
> +
> +void clar__assert(
> + int condition,
> + const char *file,
> + const char *function,
> + size_t line,
> + const char *error_msg,
> + const char *description,
> + int should_abort)
> +{
> + if (condition)
> + return;
> +
> + clar__fail(file, function, line, error_msg, description, should_abort);
> +}
> +
> +void clar__assert_equal(
> + const char *file,
> + const char *function,
> + size_t line,
> + const char *err,
> + int should_abort,
> + const char *fmt,
> + ...)
> +{
> + va_list args;
> + char buf[4096];
> + int is_equal = 1;
> +
> + va_start(args, fmt);
> +
> + if (!strcmp("%s", fmt)) {
> + const char *s1 = va_arg(args, const char *);
> + const char *s2 = va_arg(args, const char *);
> + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
> +
> + if (!is_equal) {
> + if (s1 && s2) {
> + int pos;
> + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
> + /* find differing byte offset */;
> + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
> + s1, s2, pos);
> + } else {
> + p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
> + }
> + }
> + }
> + else if(!strcmp("%.*s", fmt)) {
> + const char *s1 = va_arg(args, const char *);
> + const char *s2 = va_arg(args, const char *);
> + int len = va_arg(args, int);
> + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
> +
> + if (!is_equal) {
> + if (s1 && s2) {
> + int pos;
> + for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
> + }
> + }
> + }
> + else if (!strcmp("%ls", fmt)) {
> + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
> + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
> + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
> +
> + if (!is_equal) {
> + if (wcs1 && wcs2) {
> + int pos;
> + for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
> + /* find differing byte offset */;
> + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
> + wcs1, wcs2, pos);
> + } else {
> + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
> + }
> + }
> + }
> + else if(!strcmp("%.*ls", fmt)) {
> + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
> + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
> + int len = va_arg(args, int);
> + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
> +
> + if (!is_equal) {
> + if (wcs1 && wcs2) {
> + int pos;
> + for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
> + }
> + }
> + }
> + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
> + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
> + is_equal = (sz1 == sz2);
> + if (!is_equal) {
> + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
> + strncat(buf, " != ", sizeof(buf) - offset);
> + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
> + }
> + }
> + else if (!strcmp("%p", fmt)) {
> + 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);
> + }
> + else {
> + int i1 = va_arg(args, int), i2 = va_arg(args, int);
> + is_equal = (i1 == i2);
> + if (!is_equal) {
> + int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
> + strncat(buf, " != ", sizeof(buf) - offset);
> + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
> + }
> + }
> +
> + va_end(args);
> +
> + if (!is_equal)
> + clar__fail(file, function, line, err, buf, should_abort);
> +}
> +
> +void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
> +{
> + _clar.local_cleanup = cleanup;
> + _clar.local_cleanup_payload = opaque;
> +}
> +
> +#include "clar/sandbox.h"
> +#include "clar/fixtures.h"
> +#include "clar/fs.h"
> +#include "clar/print.h"
> +#include "clar/summary.h"
> diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
> new file mode 100644
> index 0000000000..8c22382bd5
> --- /dev/null
> +++ b/t/unit-tests/clar/clar.h
> @@ -0,0 +1,173 @@
> +/*
> + * 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_H__
> +#define __CLAR_TEST_H__
> +
> +#include <stdlib.h>
> +
> +enum cl_test_status {
> + CL_TEST_OK,
> + CL_TEST_FAILURE,
> + CL_TEST_SKIP,
> + CL_TEST_NOTRUN,
> +};
> +
> +enum cl_output_format {
> + CL_OUTPUT_CLAP,
> + CL_OUTPUT_TAP,
> +};
> +
> +/** Setup clar environment */
> +void clar_test_init(int argc, char *argv[]);
> +int clar_test_run(void);
> +void clar_test_shutdown(void);
> +
> +/** One shot setup & run */
> +int clar_test(int argc, char *argv[]);
> +
> +const char *clar_sandbox_path(void);
> +
> +void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
> +void cl_fs_cleanup(void);
> +
> +/**
> + * cl_trace_* is a hook to provide a simple global tracing
> + * mechanism.
> + *
> + * The goal here is to let main() provide clar-proper
> + * with a callback to optionally write log info for
> + * test operations into the same stream used by their
> + * actual tests. This would let them print test names
> + * and maybe performance data as they choose.
> + *
> + * The goal is NOT to alter the flow of control or to
> + * override test selection/skipping. (So the callback
> + * does not return a value.)
> + *
> + * The goal is NOT to duplicate the existing
> + * pass/fail/skip reporting. (So the callback
> + * does not accept a status/errorcode argument.)
> + *
> + */
> +typedef enum cl_trace_event {
> + CL_TRACE__SUITE_BEGIN,
> + CL_TRACE__SUITE_END,
> + CL_TRACE__TEST__BEGIN,
> + CL_TRACE__TEST__END,
> + CL_TRACE__TEST__RUN_BEGIN,
> + CL_TRACE__TEST__RUN_END,
> + CL_TRACE__TEST__LONGJMP,
> +} cl_trace_event;
> +
> +typedef void (cl_trace_cb)(
> + cl_trace_event ev,
> + const char *suite_name,
> + const char *test_name,
> + void *payload);
> +
> +/**
> + * Register a callback into CLAR to send global trace events.
> + * Pass NULL to disable.
> + */
> +void cl_trace_register(cl_trace_cb *cb, void *payload);
> +
> +
> +#ifdef CLAR_FIXTURE_PATH
> +const char *cl_fixture(const char *fixture_name);
> +void cl_fixture_sandbox(const char *fixture_name);
> +void cl_fixture_cleanup(const char *fixture_name);
> +const char *cl_fixture_basename(const char *fixture_name);
> +#endif
> +
> +/**
> + * 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)
> +
> +/**
> + * 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)
> +
> +/**
> + * Assertion macros with no error message
> + */
> +#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
> +#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
> +#define cl_assert(expr) cl_assert_(expr, NULL)
> +
> +/**
> + * Check macros with no error message
> + */
> +#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
> +#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
> +#define cl_check(expr) cl_check_(expr, NULL)
> +
> +/**
> + * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
> +
> +void clar__skip(void);
> +
> +void clar__fail(
> + const char *file,
> + const char *func,
> + size_t line,
> + const char *error,
> + const char *description,
> + int should_abort);
> +
> +void clar__assert(
> + int condition,
> + const char *file,
> + const char *func,
> + size_t line,
> + const char *error,
> + const char *description,
> + int should_abort);
> +
> +void clar__assert_equal(
> + const char *file,
> + const char *func,
> + size_t line,
> + const char *err,
> + int should_abort,
> + const char *fmt,
> + ...);
> +
> +#endif
> diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
> new file mode 100644
> index 0000000000..6ec6423484
> --- /dev/null
> +++ b/t/unit-tests/clar/clar/fixtures.h
> @@ -0,0 +1,50 @@
> +#ifdef CLAR_FIXTURE_PATH
> +static const char *
> +fixture_path(const char *base, const char *fixture_name)
> +{
> + static char _path[4096];
> + size_t root_len;
> +
> + root_len = strlen(base);
> + strncpy(_path, base, sizeof(_path));
> +
> + if (_path[root_len - 1] != '/')
> + _path[root_len++] = '/';
> +
> + if (fixture_name[0] == '/')
> + fixture_name++;
> +
> + strncpy(_path + root_len,
> + fixture_name,
> + sizeof(_path) - root_len);
> +
> + return _path;
> +}
> +
> +const char *cl_fixture(const char *fixture_name)
> +{
> + return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
> +}
> +
> +void cl_fixture_sandbox(const char *fixture_name)
> +{
> + fs_copy(cl_fixture(fixture_name), _clar_path);
> +}
> +
> +const char *cl_fixture_basename(const char *fixture_name)
> +{
> + const char *p;
> +
> + for (p = fixture_name; *p; p++) {
> + if (p[0] == '/' && p[1] && p[1] != '/')
> + fixture_name = p+1;
> + }
> +
> + return fixture_name;
> +}
> +
> +void cl_fixture_cleanup(const char *fixture_name)
> +{
> + fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
> +}
> +#endif
> diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
> new file mode 100644
> index 0000000000..3e39890bd3
> --- /dev/null
> +++ b/t/unit-tests/clar/clar/fs.h
> @@ -0,0 +1,522 @@
> +/*
> + * By default, use a read/write loop to copy files on POSIX systems.
> + * On Linux, use sendfile by default as it's slightly faster. On
> + * macOS, we avoid fcopyfile by default because it's slightly slower.
> + */
> +#undef USE_FCOPYFILE
> +#define USE_SENDFILE 1
> +
> +#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
> +
> +#ifdef __MINGW32__
> +
> +/* These security-enhanced functions are not available
> + * in MinGW, so just use the vanilla ones */
> +#define wcscpy_s(a, b, c) wcscpy((a), (c))
> +#define wcscat_s(a, b, c) wcscat((a), (c))
> +
> +#endif /* __MINGW32__ */
> +
> +static int
> +fs__dotordotdot(WCHAR *_tocheck)
> +{
> + return _tocheck[0] == '.' &&
> + (_tocheck[1] == '\0' ||
> + (_tocheck[1] == '.' && _tocheck[2] == '\0'));
> +}
> +
> +static int
> +fs_rmdir_rmdir(WCHAR *_wpath)
> +{
> + unsigned retries = 1;
> +
> + while (!RemoveDirectoryW(_wpath)) {
> + /* Only retry when we have retries remaining, and the
> + * error was ERROR_DIR_NOT_EMPTY. */
> + if (retries++ > RM_RETRY_COUNT ||
> + ERROR_DIR_NOT_EMPTY != GetLastError())
> + return -1;
> +
> + /* Give whatever has a handle to a child item some time
> + * to release it before trying again */
> + Sleep(RM_RETRY_DELAY * retries * retries);
> + }
> +
> + return 0;
> +}
> +
> +static void translate_path(WCHAR *path, size_t path_size)
> +{
> + size_t path_len, i;
> +
> + if (wcsncmp(path, L"\\\\?\\", 4) == 0)
> + return;
> +
> + path_len = wcslen(path);
> + cl_assert(path_size > path_len + 4);
> +
> + for (i = path_len; i > 0; i--) {
> + WCHAR c = path[i - 1];
> +
> + if (c == L'/')
> + path[i + 3] = L'\\';
> + else
> + path[i + 3] = path[i - 1];
> + }
> +
> + path[0] = L'\\';
> + path[1] = L'\\';
> + path[2] = L'?';
> + path[3] = L'\\';
> + path[path_len + 4] = L'\0';
> +}
> +
> +static void
> +fs_rmdir_helper(WCHAR *_wsource)
> +{
> + WCHAR buffer[CLAR_MAX_PATH];
> + HANDLE find_handle;
> + WIN32_FIND_DATAW find_data;
> + size_t buffer_prefix_len;
> +
> + /* Set up the buffer and capture the length */
> + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
> + translate_path(buffer, CLAR_MAX_PATH);
> + wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
> + buffer_prefix_len = wcslen(buffer);
> +
> + /* FindFirstFile needs a wildcard to match multiple items */
> + wcscat_s(buffer, CLAR_MAX_PATH, L"*");
> + find_handle = FindFirstFileW(buffer, &find_data);
> + cl_assert(INVALID_HANDLE_VALUE != find_handle);
> +
> + do {
> + /* FindFirstFile/FindNextFile gives back . and ..
> + * entries at the beginning */
> + if (fs__dotordotdot(find_data.cFileName))
> + continue;
> +
> + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
> +
> + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
> + fs_rmdir_helper(buffer);
> + else {
> + /* If set, the +R bit must be cleared before deleting */
> + if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
> + cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
> +
> + cl_assert(DeleteFileW(buffer));
> + }
> + }
> + while (FindNextFileW(find_handle, &find_data));
> +
> + /* Ensure that we successfully completed the enumeration */
> + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
> +
> + /* Close the find handle */
> + FindClose(find_handle);
> +
> + /* Now that the directory is empty, remove it */
> + cl_assert(0 == fs_rmdir_rmdir(_wsource));
> +}
> +
> +static int
> +fs_rm_wait(WCHAR *_wpath)
> +{
> + unsigned retries = 1;
> + DWORD last_error;
> +
> + do {
> + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
> + last_error = GetLastError();
> + else
> + last_error = ERROR_SUCCESS;
> +
> + /* Is the item gone? */
> + if (ERROR_FILE_NOT_FOUND == last_error ||
> + ERROR_PATH_NOT_FOUND == last_error)
> + return 0;
> +
> + Sleep(RM_RETRY_DELAY * retries * retries);
> + }
> + while (retries++ <= RM_RETRY_COUNT);
> +
> + return -1;
> +}
> +
> +static void
> +fs_rm(const char *_source)
> +{
> + WCHAR wsource[CLAR_MAX_PATH];
> + DWORD attrs;
> +
> + /* The input path is UTF-8. Convert it to wide characters
> + * for use with the Windows API */
> + cl_assert(MultiByteToWideChar(CP_UTF8,
> + MB_ERR_INVALID_CHARS,
> + _source,
> + -1, /* Indicates NULL termination */
> + wsource,
> + CLAR_MAX_PATH));
> +
> + translate_path(wsource, CLAR_MAX_PATH);
> +
> + /* Does the item exist? If not, we have no work to do */
> + attrs = GetFileAttributesW(wsource);
> +
> + if (INVALID_FILE_ATTRIBUTES == attrs)
> + return;
> +
> + if (FILE_ATTRIBUTE_DIRECTORY & attrs)
> + fs_rmdir_helper(wsource);
> + else {
> + /* The item is a file. Strip the +R bit */
> + if (FILE_ATTRIBUTE_READONLY & attrs)
> + cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
> +
> + cl_assert(DeleteFileW(wsource));
> + }
> +
> + /* Wait for the DeleteFile or RemoveDirectory call to complete */
> + cl_assert(0 == fs_rm_wait(wsource));
> +}
> +
> +static void
> +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
> +{
> + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
> + HANDLE find_handle;
> + WIN32_FIND_DATAW find_data;
> + size_t buf_source_prefix_len, buf_dest_prefix_len;
> +
> + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
> + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
> + translate_path(buf_source, CLAR_MAX_PATH);
> + buf_source_prefix_len = wcslen(buf_source);
> +
> + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
> + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
> + translate_path(buf_dest, CLAR_MAX_PATH);
> + buf_dest_prefix_len = wcslen(buf_dest);
> +
> + /* Get an enumerator for the items in the source. */
> + wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
> + find_handle = FindFirstFileW(buf_source, &find_data);
> + cl_assert(INVALID_HANDLE_VALUE != find_handle);
> +
> + /* Create the target directory. */
> + cl_assert(CreateDirectoryW(_wdest, NULL));
> +
> + do {
> + /* FindFirstFile/FindNextFile gives back . and ..
> + * entries at the beginning */
> + if (fs__dotordotdot(find_data.cFileName))
> + continue;
> +
> + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
> + wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
> +
> + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
> + fs_copydir_helper(buf_source, buf_dest);
> + else
> + cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
> + }
> + while (FindNextFileW(find_handle, &find_data));
> +
> + /* Ensure that we successfully completed the enumeration */
> + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
> +
> + /* Close the find handle */
> + FindClose(find_handle);
> +}
> +
> +static void
> +fs_copy(const char *_source, const char *_dest)
> +{
> + WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
> + DWORD source_attrs, dest_attrs;
> + HANDLE find_handle;
> + WIN32_FIND_DATAW find_data;
> +
> + /* The input paths are UTF-8. Convert them to wide characters
> + * for use with the Windows API. */
> + cl_assert(MultiByteToWideChar(CP_UTF8,
> + MB_ERR_INVALID_CHARS,
> + _source,
> + -1,
> + wsource,
> + CLAR_MAX_PATH));
> +
> + cl_assert(MultiByteToWideChar(CP_UTF8,
> + MB_ERR_INVALID_CHARS,
> + _dest,
> + -1,
> + wdest,
> + CLAR_MAX_PATH));
> +
> + translate_path(wsource, CLAR_MAX_PATH);
> + translate_path(wdest, CLAR_MAX_PATH);
> +
> + /* Check the source for existence */
> + source_attrs = GetFileAttributesW(wsource);
> + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
> +
> + /* Check the target for existence */
> + dest_attrs = GetFileAttributesW(wdest);
> +
> + if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
> + /* Target exists; append last path part of source to target.
> + * Use FindFirstFile to parse the path */
> + find_handle = FindFirstFileW(wsource, &find_data);
> + cl_assert(INVALID_HANDLE_VALUE != find_handle);
> + wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
> + wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
> + FindClose(find_handle);
> +
> + /* Check the new target for existence */
> + cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
> + }
> +
> + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
> + fs_copydir_helper(wsource, wdest);
> + else
> + cl_assert(CopyFileW(wsource, wdest, TRUE));
> +}
> +
> +void
> +cl_fs_cleanup(void)
> +{
> +#ifdef CLAR_FIXTURE_PATH
> + fs_rm(fixture_path(_clar_path, "*"));
> +#endif
> +}
> +
> +#else
> +
> +#include <errno.h>
> +#include <string.h>
> +#include <limits.h>
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +
> +#if defined(__linux__)
> +# include <sys/sendfile.h>
> +#endif
> +
> +#if defined(__APPLE__)
> +# include <copyfile.h>
> +#endif
> +
> +static void basename_r(const char **out, int *out_len, const char *in)
> +{
> + size_t in_len = strlen(in), start_pos;
> +
> + for (in_len = strlen(in); in_len; in_len--) {
> + if (in[in_len - 1] != '/')
> + break;
> + }
> +
> + for (start_pos = in_len; start_pos; start_pos--) {
> + if (in[start_pos - 1] == '/')
> + break;
> + }
> +
> + cl_assert(in_len - start_pos < INT_MAX);
> +
> + if (in_len - start_pos > 0) {
> + *out = &in[start_pos];
> + *out_len = (in_len - start_pos);
> + } else {
> + *out = "/";
> + *out_len = 1;
> + }
> +}
> +
> +static char *joinpath(const char *dir, const char *base, int base_len)
> +{
> + char *out;
> + int len;
> +
> + if (base_len == -1) {
> + size_t bl = strlen(base);
> +
> + cl_assert(bl < INT_MAX);
> + base_len = (int)bl;
> + }
> +
> + len = strlen(dir) + base_len + 2;
> + cl_assert(len > 0);
> +
> + cl_assert(out = malloc(len));
> + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
> +
> + return out;
> +}
> +
> +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");
> + while ((d = (errno = 0, readdir(source_dir))) != NULL) {
> + char *child;
> +
> + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
> + continue;
> +
> + child = joinpath(source, d->d_name, -1);
> + fs_copy(child, dest);
> + free(child);
> + }
> +
> + cl_assert_(errno == 0, "Failed to iterate source dir");
> +
> + closedir(source_dir);
> +}
> +
> +static void
> +fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
> +{
> + int in, out;
> +
> + cl_must_pass((in = open(source, O_RDONLY)));
> + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
> +
> +#if USE_FCOPYFILE && defined(__APPLE__)
> + ((void)(source_len)); /* unused */
> + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
> +#elif USE_SENDFILE && defined(__linux__)
> + {
> + ssize_t ret = 0;
> +
> + while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
> + source_len -= (size_t)ret;
> + }
> + cl_assert(ret >= 0);
> + }
> +#else
> + {
> + char buf[131072];
> + ssize_t ret;
> +
> + ((void)(source_len)); /* unused */
> +
> + while ((ret = read(in, buf, sizeof(buf))) > 0) {
> + size_t len = (size_t)ret;
> +
> + while (len && (ret = write(out, buf, len)) > 0) {
> + cl_assert(ret <= (ssize_t)len);
> + len -= ret;
> + }
> + cl_assert(ret >= 0);
> + }
> + cl_assert(ret == 0);
> + }
> +#endif
> +
> + close(in);
> + close(out);
> +}
> +
> +static void
> +fs_copy(const char *source, const char *_dest)
> +{
> + char *dbuf = NULL;
> + const char *dest = NULL;
> + struct stat source_st, dest_st;
> +
> + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
> +
> + if (lstat(_dest, &dest_st) == 0) {
> + const char *base;
> + int base_len;
> +
> + /* Target exists and is directory; append basename */
> + cl_assert(S_ISDIR(dest_st.st_mode));
> +
> + basename_r(&base, &base_len, source);
> + cl_assert(base_len < INT_MAX);
> +
> + dbuf = joinpath(_dest, base, base_len);
> + dest = dbuf;
> + } else if (errno != ENOENT) {
> + cl_fail("Cannot copy; cannot stat destination");
> + } else {
> + dest = _dest;
> + }
> +
> + if (S_ISDIR(source_st.st_mode)) {
> + fs_copydir_helper(source, dest, source_st.st_mode);
> + } else {
> + fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
> + }
> +
> + free(dbuf);
> +}
> +
> +static void
> +fs_rmdir_helper(const char *path)
> +{
> + DIR *dir;
> + struct dirent *d;
> +
> + cl_assert_(dir = opendir(path), "Could not open dir");
> + while ((d = (errno = 0, readdir(dir))) != NULL) {
> + char *child;
> +
> + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
> + continue;
> +
> + child = joinpath(path, d->d_name, -1);
> + fs_rm(child);
> + free(child);
> + }
> +
> + cl_assert_(errno == 0, "Failed to iterate source dir");
> + closedir(dir);
> +
> + cl_must_pass_(rmdir(path), "Could not remove directory");
> +}
> +
> +static void
> +fs_rm(const char *path)
> +{
> + struct stat st;
> +
> + if (lstat(path, &st)) {
> + if (errno == ENOENT)
> + return;
> +
> + cl_fail("Cannot copy; cannot stat destination");
> + }
> +
> + if (S_ISDIR(st.st_mode)) {
> + fs_rmdir_helper(path);
> + } else {
> + cl_must_pass(unlink(path));
> + }
> +}
> +
> +void
> +cl_fs_cleanup(void)
> +{
> + clar_unsandbox();
> + clar_sandbox();
> +}
> +#endif
> diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
> new file mode 100644
> index 0000000000..c17e2f693b
> --- /dev/null
> +++ b/t/unit-tests/clar/clar/print.h
> @@ -0,0 +1,211 @@
> +/* clap: clar protocol, the traditional clar output format */
> +
> +static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
> +{
> + (void)test_count;
> + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
> + printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
> +}
> +
> +static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
> +{
> + (void)test_count;
> + (void)suite_count;
> + (void)error_count;
> +
> + printf("\n\n");
> + clar_report_all();
> +}
> +
> +static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
> +{
> + printf(" %d) Failure:\n", num);
> +
> + printf("%s::%s [%s:%"PRIuZ"]\n",
> + report->suite,
> + report->test,
> + error->file,
> + error->line_number);
> +
> + printf(" %s\n", error->error_msg);
> +
> + if (error->description != NULL)
> + printf(" %s\n", error->description);
> +
> + printf("\n");
> + fflush(stdout);
> +}
> +
> +static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
> +{
> + (void)test_name;
> + (void)test_number;
> +
> + 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;
> + }
> + } else {
> + switch (status) {
> + case CL_TEST_OK: printf("."); break;
> + case CL_TEST_FAILURE: printf("F"); break;
> + case CL_TEST_SKIP: printf("S"); break;
> + case CL_TEST_NOTRUN: printf("N"); break;
> + }
> +
> + fflush(stdout);
> + }
> +}
> +
> +static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
> +{
> + if (_clar.verbosity == 1)
> + printf("\n%s", suite_name);
> +
> + (void)suite_index;
> +}
> +
> +static void clar_print_clap_onabort(const char *fmt, va_list arg)
> +{
> + vfprintf(stderr, fmt, arg);
> +}
> +
> +/* tap: test anywhere protocol format */
> +
> +static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
> +{
> + (void)test_count;
> + (void)suite_count;
> + (void)suite_names;
> + printf("TAP version 13\n");
> +}
> +
> +static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
> +{
> + (void)suite_count;
> + (void)error_count;
> +
> + printf("1..%d\n", test_count);
> +}
> +
> +static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
> +{
> + (void)num;
> + (void)report;
> + (void)error;
> +}
> +
> +static void print_escaped(const char *str)
> +{
> + char *c;
> +
> + while ((c = strchr(str, '\'')) != NULL) {
> + printf("%.*s", (int)(c - str), str);
> + printf("''");
> + str = c + 1;
> + }
> +
> + printf("%s", str);
> +}
> +
> +static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
> +{
> + const struct clar_error *error = _clar.last_report->errors;
> +
> + (void)test_name;
> + (void)test_number;
> +
> + switch(status) {
> + case CL_TEST_OK:
> + printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
> + break;
> + 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 (error->description)
> + printf(" %s\n", error->description);
> +
> + printf(" at:\n");
> + printf(" file: '"); print_escaped(error->file); printf("'\n");
> + printf(" line: %" PRIuZ "\n", error->line_number);
> + printf(" function: '%s'\n", error->function);
> + printf(" ---\n");
> +
> + break;
> + case CL_TEST_SKIP:
> + case CL_TEST_NOTRUN:
> + printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
> + break;
> + }
> +
> + fflush(stdout);
> +}
> +
> +static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
> +{
> + printf("# start of suite %d: %s\n", suite_index, suite_name);
> +}
> +
> +static void clar_print_tap_onabort(const char *fmt, va_list arg)
> +{
> + printf("Bail out! ");
> + vprintf(fmt, arg);
> + fflush(stdout);
> +}
> +
> +/* indirection between protocol output selection */
> +
> +#define PRINT(FN, ...) do { \
> + switch (_clar.output_format) { \
> + case CL_OUTPUT_CLAP: \
> + clar_print_clap_##FN (__VA_ARGS__); \
> + break; \
> + case CL_OUTPUT_TAP: \
> + clar_print_tap_##FN (__VA_ARGS__); \
> + break; \
> + default: \
> + abort(); \
> + } \
> + } while (0)
> +
> +static void clar_print_init(int test_count, int suite_count, const char *suite_names)
> +{
> + PRINT(init, test_count, suite_count, suite_names);
> +}
> +
> +static void clar_print_shutdown(int test_count, int suite_count, int error_count)
> +{
> + PRINT(shutdown, test_count, suite_count, error_count);
> +}
> +
> +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
> +{
> + PRINT(error, num, report, error);
> +}
> +
> +static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
> +{
> + PRINT(ontest, suite_name, test_name, test_number, status);
> +}
> +
> +static void clar_print_onsuite(const char *suite_name, int suite_index)
> +{
> + PRINT(onsuite, suite_name, suite_index);
> +}
> +
> +static void clar_print_onabort(const char *msg, ...)
> +{
> + va_list argp;
> + va_start(argp, msg);
> + PRINT(onabort, msg, argp);
> + va_end(argp);
> +}
> diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
> new file mode 100644
> index 0000000000..7c177f3525
> --- /dev/null
> +++ b/t/unit-tests/clar/clar/sandbox.h
> @@ -0,0 +1,153 @@
> +#ifdef __APPLE__
> +#include <sys/syslimits.h>
> +#endif
> +
> +static char _clar_path[4096 + 1];
> +
> +static int
> +is_valid_tmp_path(const char *path)
> +{
> + STAT_T st;
> +
> + if (stat(path, &st) != 0)
> + return 0;
> +
> + if (!S_ISDIR(st.st_mode))
> + return 0;
> +
> + return (access(path, W_OK) == 0);
> +}
> +
> +static int
> +find_tmp_path(char *buffer, size_t length)
> +{
> +#ifndef _WIN32
> + static const size_t var_count = 5;
> + static const char *env_vars[] = {
> + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
> + };
> +
> + size_t i;
> +
> + 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;
> + }
> + }
> +
> + /* 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)
> + return 0;
> +
> + if (GetTempPath((DWORD)length, buffer))
> + return 0;
> +#endif
> +
> + /* This system doesn't like us, try to use the current directory */
> + if (is_valid_tmp_path(".")) {
> + strncpy(buffer, ".", length - 1);
> + buffer[length - 1] = '\0';
> + return 0;
> + }
> +
> + return -1;
> +}
> +
> +static void clar_unsandbox(void)
> +{
> + if (_clar_path[0] == '\0')
> + return;
> +
> + cl_must_pass(chdir(".."));
> +
> + fs_rm(_clar_path);
> +}
> +
> +static int build_sandbox_path(void)
> +{
> +#ifdef CLAR_TMPDIR
> + const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
> +#else
> + const char path_tail[] = "clar_tmp_XXXXXX";
> +#endif
> +
> + size_t len;
> +
> + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
> + return -1;
> +
> + len = strlen(_clar_path);
> +
> +#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 (_clar_path[len - 1] != '/') {
> + _clar_path[len++] = '/';
> + }
> +
> + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
> +
> +#if defined(__MINGW32__)
> + if (_mktemp(_clar_path) == NULL)
> + return -1;
> +
> + if (mkdir(_clar_path, 0700) != 0)
> + return -1;
> +#elif defined(_WIN32)
> + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
> + return -1;
> +
> + if (mkdir(_clar_path, 0700) != 0)
> + return -1;
> +#else
> + if (mkdtemp(_clar_path) == NULL)
> + return -1;
> +#endif
> +
> + return 0;
> +}
> +
> +static int clar_sandbox(void)
> +{
> + if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
> + return -1;
> +
> + if (chdir(_clar_path) != 0)
> + return -1;
> +
> + return 0;
> +}
> +
> +const char *clar_sandbox_path(void)
> +{
> + return _clar_path;
> +}
> diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
> new file mode 100644
> index 0000000000..4dd352e28b
> --- /dev/null
> +++ b/t/unit-tests/clar/clar/summary.h
> @@ -0,0 +1,143 @@
> +
> +#include <stdio.h>
> +#include <time.h>
> +
> +static int clar_summary_close_tag(
> + struct clar_summary *summary, const char *tag, int indent)
> +{
> + const char *indt;
> +
> + if (indent == 0) indt = "";
> + else if (indent == 1) indt = "\t";
> + else indt = "\t\t";
> +
> + return fprintf(summary->fp, "%s</%s>\n", indt, tag);
> +}
> +
> +static int clar_summary_testsuites(struct clar_summary *summary)
> +{
> + return fprintf(summary->fp, "<testsuites>\n");
> +}
> +
> +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(×tamp);
> + char iso_dt[20];
> +
> + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
> + return -1;
> +
> + return fprintf(summary->fp, "\t<testsuite"
> + " id=\"%d\""
> + " name=\"%s\""
> + " hostname=\"localhost\""
> + " timestamp=\"%s\""
> + " tests=\"%d\""
> + " failures=\"%d\""
> + " errors=\"%d\">\n",
> + idn, name, iso_dt, test_count, fail_count, error_count);
> +}
> +
> +static int clar_summary_testcase(struct clar_summary *summary,
> + const char *name, const char *classname, double elapsed)
> +{
> + return fprintf(summary->fp,
> + "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
> + name, classname, elapsed);
> +}
> +
> +static int clar_summary_failure(struct clar_summary *summary,
> + const char *type, const char *message, const char *desc)
> +{
> + return fprintf(summary->fp,
> + "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
> + type, message, desc);
> +}
> +
> +static int clar_summary_skipped(struct clar_summary *summary)
> +{
> + return fprintf(summary->fp, "\t\t\t<skipped />\n");
> +}
> +
> +struct clar_summary *clar_summary_init(const char *filename)
> +{
> + struct clar_summary *summary;
> + FILE *fp;
> +
> + if ((fp = fopen(filename, "w")) == NULL) {
> + perror("fopen");
> + return NULL;
> + }
> +
> + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
> + perror("malloc");
> + fclose(fp);
> + return NULL;
> + }
> +
> + summary->filename = filename;
> + summary->fp = fp;
> +
> + return summary;
> +}
> +
> +int clar_summary_shutdown(struct clar_summary *summary)
> +{
> + struct clar_report *report;
> + const char *last_suite = NULL;
> +
> + if (clar_summary_testsuites(summary) < 0)
> + goto on_error;
> +
> + report = _clar.reports;
> + while (report != NULL) {
> + struct clar_error *error = report->errors;
> +
> + if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
> + if (clar_summary_testsuite(summary, 0, report->suite,
> + report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
> + goto on_error;
> + }
> +
> + last_suite = report->suite;
> +
> + clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
> +
> + while (error != NULL) {
> + if (clar_summary_failure(summary, "assert",
> + error->error_msg, error->description) < 0)
> + goto on_error;
> +
> + error = error->next;
> + }
> +
> + if (report->status == CL_TEST_SKIP)
> + clar_summary_skipped(summary);
> +
> + if (clar_summary_close_tag(summary, "testcase", 2) < 0)
> + goto on_error;
> +
> + report = report->next;
> +
> + if (!report || strcmp(last_suite, report->suite) != 0) {
> + if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
> + goto on_error;
> + }
> + }
> +
> + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
> + fclose(summary->fp) != 0)
> + goto on_error;
> +
> + printf("written summary file to %s\n", summary->filename);
> +
> + free(summary);
> + return 0;
> +
> +on_error:
> + fclose(summary->fp);
> + free(summary);
> + return -1;
> +}
> diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
> new file mode 100755
> index 0000000000..80996ac3e7
> --- /dev/null
> +++ b/t/unit-tests/clar/generate.py
> @@ -0,0 +1,266 @@
> +#!/usr/bin/env python
> +#
> +# 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.
> +#
> +
> +from __future__ import with_statement
> +from string import Template
> +import re, fnmatch, os, sys, codecs, pickle
> +
> +class Module(object):
> + class Template(object):
> + def __init__(self, module):
> + self.module = module
> +
> + def _render_callback(self, cb):
> + if not cb:
> + return ' { NULL, NULL }'
> + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
> +
> + class DeclarationTemplate(Template):
> + def render(self):
> + out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
> +
> + for initializer in self.module.initializers:
> + out += "extern %s;\n" % initializer['declaration']
> +
> + if self.module.cleanup:
> + out += "extern %s;\n" % self.module.cleanup['declaration']
> +
> + return out
> +
> + class CallbacksTemplate(Template):
> + def render(self):
> + out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
> + out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
> + out += "\n};\n"
> + return out
> +
> + class InfoTemplate(Template):
> + def render(self):
> + templates = []
> +
> + initializers = self.module.initializers
> + if len(initializers) == 0:
> + initializers = [ None ]
> +
> + for initializer in initializers:
> + name = self.module.clean_name()
> + if initializer and initializer['short_name'].startswith('initialize_'):
> + variant = initializer['short_name'][len('initialize_'):]
> + name += " (%s)" % variant.replace('_', ' ')
> +
> + template = Template(
> + r"""
> + {
> + "${clean_name}",
> + ${initialize},
> + ${cleanup},
> + ${cb_ptr}, ${cb_count}, ${enabled}
> + }"""
> + ).substitute(
> + clean_name = name,
> + initialize = self._render_callback(initializer),
> + cleanup = self._render_callback(self.module.cleanup),
> + cb_ptr = "_clar_cb_%s" % self.module.name,
> + cb_count = len(self.module.callbacks),
> + enabled = int(self.module.enabled)
> + )
> + templates.append(template)
> +
> + return ','.join(templates)
> +
> + def __init__(self, name):
> + self.name = name
> +
> + self.mtime = None
> + self.enabled = True
> + self.modified = False
> +
> + def clean_name(self):
> + return self.name.replace("_", "::")
> +
> + def _skip_comments(self, text):
> + SKIP_COMMENTS_REGEX = re.compile(
> + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
> + re.DOTALL | re.MULTILINE)
> +
> + def _replacer(match):
> + s = match.group(0)
> + return "" if s.startswith('/') else s
> +
> + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
> +
> + def parse(self, contents):
> + TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
> +
> + contents = self._skip_comments(contents)
> + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
> +
> + self.callbacks = []
> + self.initializers = []
> + self.cleanup = None
> +
> + for (declaration, symbol, short_name) in regex.findall(contents):
> + data = {
> + "short_name" : short_name,
> + "declaration" : declaration,
> + "symbol" : symbol
> + }
> +
> + if short_name.startswith('initialize'):
> + self.initializers.append(data)
> + elif short_name == 'cleanup':
> + self.cleanup = data
> + else:
> + self.callbacks.append(data)
> +
> + return self.callbacks != []
> +
> + def refresh(self, path):
> + self.modified = False
> +
> + try:
> + st = os.stat(path)
> +
> + # Not modified
> + if st.st_mtime == self.mtime:
> + return True
> +
> + self.modified = True
> + self.mtime = st.st_mtime
> +
> + with codecs.open(path, encoding='utf-8') as fp:
> + raw_content = fp.read()
> +
> + except IOError:
> + return False
> +
> + return self.parse(raw_content)
> +
> +class TestSuite(object):
> +
> + def __init__(self, path, output):
> + self.path = path
> + self.output = output
> +
> + def should_generate(self, path):
> + if not os.path.isfile(path):
> + return True
> +
> + if any(module.modified for module in self.modules.values()):
> + return True
> +
> + return False
> +
> + def find_modules(self):
> + modules = []
> + for root, _, files in os.walk(self.path):
> + module_root = root[len(self.path):]
> + module_root = [c for c in module_root.split(os.sep) if c]
> +
> + tests_in_module = fnmatch.filter(files, "*.c")
> +
> + for test_file in tests_in_module:
> + full_path = os.path.join(root, test_file)
> + module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
> +
> + modules.append((full_path, module_name))
> +
> + return modules
> +
> + def load_cache(self):
> + path = os.path.join(self.output, '.clarcache')
> + cache = {}
> +
> + try:
> + fp = open(path, 'rb')
> + cache = pickle.load(fp)
> + fp.close()
> + except (IOError, ValueError):
> + pass
> +
> + return cache
> +
> + def save_cache(self):
> + path = os.path.join(self.output, '.clarcache')
> + with open(path, 'wb') as cache:
> + pickle.dump(self.modules, cache)
> +
> + def load(self, force = False):
> + module_data = self.find_modules()
> + self.modules = {} if force else self.load_cache()
> +
> + for path, name in module_data:
> + if name not in self.modules:
> + self.modules[name] = Module(name)
> +
> + if not self.modules[name].refresh(path):
> + del self.modules[name]
> +
> + def disable(self, excluded):
> + for exclude in excluded:
> + for module in self.modules.values():
> + name = module.clean_name()
> + if name.startswith(exclude):
> + module.enabled = False
> + module.modified = True
> +
> + def suite_count(self):
> + return sum(max(1, len(m.initializers)) for m in self.modules.values())
> +
> + def callback_count(self):
> + return sum(len(module.callbacks) for module in self.modules.values())
> +
> + def write(self):
> + output = os.path.join(self.output, 'clar.suite')
> +
> + if not self.should_generate(output):
> + return False
> +
> + with open(output, 'w') as data:
> + modules = sorted(self.modules.values(), key=lambda module: module.name)
> +
> + for module in modules:
> + t = Module.DeclarationTemplate(module)
> + data.write(t.render())
> +
> + for module in modules:
> + t = Module.CallbacksTemplate(module)
> + data.write(t.render())
> +
> + suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
> + Module.InfoTemplate(module).render() for module in modules
> + ) + "\n};\n"
> +
> + data.write(suites)
> +
> + data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
> + data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
> +
> + self.save_cache()
> + return True
> +
> +if __name__ == '__main__':
> + from optparse import OptionParser
> +
> + parser = OptionParser()
> + parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
> + parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
> + parser.add_option('-o', '--output', dest='output')
> +
> + options, args = parser.parse_args()
> + if len(args) > 1:
> + print("More than one path given")
> + sys.exit(1)
> +
> + path = args.pop() if args else '.'
> + output = options.output or path
> + suite = TestSuite(path, output)
> + suite.load(options.force)
> + suite.disable(options.excluded)
> + if suite.write():
> + print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
> diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
> new file mode 100644
> index 0000000000..a477d0c40c
> --- /dev/null
> +++ b/t/unit-tests/clar/test/.gitignore
> @@ -0,0 +1,4 @@
> +clar.suite
> +.clarcache
> +clar_test
> +*.o
> diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
> new file mode 100644
> index 0000000000..93c6b2ad32
> --- /dev/null
> +++ b/t/unit-tests/clar/test/Makefile
> @@ -0,0 +1,39 @@
> +#
> +# 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.
> +#
> +
> +#
> +# Set up the path to the clar sources and to the fixtures directory
> +#
> +# The fixture path needs to be an absolute path so it can be used
> +# even after we have chdir'ed into the test directory while testing.
> +#
> +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
> +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
> +CLAR_PATH := $(dir $(TEST_DIRECTORY))
> +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
> +
> +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
> +
> +.PHONY: clean
> +
> +# list the objects that go into our test
> +objects = main.o sample.o
> +
> +# build the test executable itself
> +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
> + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
> +
> +# test object files depend on clar macros
> +$(objects) : $(CLAR_PATH)clar.h
> +
> +# build the clar.suite file of test metadata
> +clar.suite:
> + python "$(CLAR_PATH)generate.py" .
> +
> +# remove all generated files
> +clean:
> + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
> diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
> new file mode 100644
> index 0000000000..0fcaa639aa
> --- /dev/null
> +++ b/t/unit-tests/clar/test/clar_test.h
> @@ -0,0 +1,16 @@
> +/*
> + * 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/main.c b/t/unit-tests/clar/test/main.c
> new file mode 100644
> index 0000000000..59e56ad255
> --- /dev/null
> +++ b/t/unit-tests/clar/test/main.c
> @@ -0,0 +1,40 @@
> +/*
> + * 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_test.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;
> +
> +#ifdef _WIN32
> +int __cdecl main(int argc, char *argv[])
> +#else
> +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);
> +
> + /* Your custom cleanup here */
> + cl_assert_equal_i(8, global_test_counter);
> +
> + return ret;
> +}
> diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
> new file mode 100644
> index 0000000000..a4d91b72fa
> --- /dev/null
> +++ b/t/unit-tests/clar/test/main.c.sample
> @@ -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_test.h"
> +
> +/*
> + * Minimal main() for clar tests.
> + *
> + * Modify this with any application specific setup or teardown that you need.
> + * 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, 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
> +{
> + /* Run the test suite */
> + return clar_test(argc, argv);
> +}
> diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
> new file mode 100644
> index 0000000000..220f4aa98a
> --- /dev/null
> +++ b/t/unit-tests/clar/test/resources/test/file
> @@ -0,0 +1 @@
> +File
> diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
> new file mode 100644
> index 0000000000..faa1209262
> --- /dev/null
> +++ b/t/unit-tests/clar/test/sample.c
> @@ -0,0 +1,84 @@
> +#include "clar_test.h"
> +#include <sys/stat.h>
> +
> +static int file_size(const char *filename)
> +{
> + struct stat st;
> +
> + if (stat(filename, &st) == 0)
> + return (int)st.st_size;
> + return -1;
> +}
> +
> +void test_sample__initialize(void)
> +{
> + global_test_counter++;
> +}
> +
> +void test_sample__cleanup(void)
> +{
> + cl_fixture_cleanup("test");
> +
> + cl_assert(file_size("test/file") == -1);
> +}
> +
> +void test_sample__1(void)
> +{
> + cl_assert(1);
> + cl_must_pass(0); /* 0 == success */
> + cl_must_fail(-1); /* <0 == failure */
> + cl_must_pass(-1); /* demonstrate a failing call */
> +}
> +
> +void test_sample__2(void)
> +{
> + cl_fixture_sandbox("test");
> +
> + cl_assert(file_size("test/nonexistent") == -1);
> + cl_assert(file_size("test/file") > 0);
> + cl_assert(100 == 101);
> +}
> +
> +void test_sample__strings(void)
> +{
> + const char *actual = "expected";
> + cl_assert_equal_s("expected", actual);
> + cl_assert_equal_s_("expected", actual, "second try with annotation");
> + cl_assert_equal_s_("mismatched", actual, "this one fails");
> +}
> +
> +void test_sample__strings_with_length(void)
> +{
> + const char *actual = "expected";
> + cl_assert_equal_strn("expected_", actual, 8);
> + cl_assert_equal_strn("exactly", actual, 2);
> + cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
> + cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
> +}
> +
> +void test_sample__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)
> +{
> + int value = 100;
> + cl_assert_equal_i_fmt(022, value, "%04o");
> +}
> +
> +void test_sample__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)
> +{
> + const char *actual = "expected";
> + cl_assert_equal_p(actual, actual); /* pointers to same object */
> + cl_assert_equal_p(&actual, actual);
> +}
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-16 13:37 ` Phillip Wood
@ 2024-08-23 12:16 ` Johannes Schindelin
2024-08-28 13:20 ` Phillip Wood
0 siblings, 1 reply; 172+ messages in thread
From: Johannes Schindelin @ 2024-08-23 12:16 UTC (permalink / raw)
To: Phillip Wood
Cc: Patrick Steinhardt, git, René Scharfe, Junio C Hamano,
Kyle Lippincott, Josh Steadmon, rsbecker, Edward Thomson
Hi Phillip,
thank you for looping me in; I meant to answer earlier.
I am very, very happy to see that there is a chance to adopt a test
framework like `clar`. Therefore, I am very interested in assisting in
every which way I can.
On Fri, 16 Aug 2024, Phillip Wood wrote:
> Johannes - are these format specifiers supported by the version of MSVCRT that
> is used by git for windows? c.f.
> https://lore.kernel.org/git/nycvar.QRO.7.76.6.2203241456250.388@tvgsbejvaqbjf.bet/
The format specifiers _should_ work well both with GCC and Visual C. What
did not work well was using `struct stat` with `_stat()` (note that the
latter has a leading underscore and the former does not).
There were other issues, top of which was the bit tricky part to adapt the
`CMakeLists.txt` definition, in particular the part where `clar.suite` is
generated. But I managed to squeeze in that work somehow and Patrick was
kind enough to already integrate the patches.
Ciao,
Johannes
> On 16/08/2024 08:04, Patrick Steinhardt wrote:
> > diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
> > [...]
> > +#ifdef _WIN32
> > [...]
> > +# ifndef PRIuZ
> > +# define PRIuZ "Iu"
> > +# endif
> > +# ifndef PRIxZ
> > +# define PRIxZ "Ix"
> > +# endif
>
> Thanks
>
> Phillip
>
> > +# if defined(_MSC_VER) || defined(__MINGW32__)
> > + typedef struct stat STAT_T;
> > +# else
> > + typedef struct _stat STAT_T;
> > +# endif
> > +#else
> > +# include <sys/wait.h> /* waitpid(2) */
> > +# include <unistd.h>
> > +# define _MAIN_CC
> > +# define p_snprintf snprintf
> > +# ifndef PRIuZ
> > +# define PRIuZ "zu"
> > +# endif
> > +# ifndef PRIxZ
> > +# define PRIxZ "zx"
> > +# endif
> > + typedef struct stat STAT_T;
> > +#endif
> > +
> > +#define MAX(x, y) (((x) > (y)) ? (x) : (y))
> > +
> > +#include "clar.h"
> > +
> > +static void fs_rm(const char *_source);
> > +static void fs_copy(const char *_source, const char *dest);
> > +
> > +#ifdef CLAR_FIXTURE_PATH
> > +static const char *
> > +fixture_path(const char *base, const char *fixture_name);
> > +#endif
> > +
> > +struct clar_error {
> > + const char *file;
> > + const char *function;
> > + size_t line_number;
> > + const char *error_msg;
> > + char *description;
> > +
> > + struct clar_error *next;
> > +};
> > +
> > +struct clar_explicit {
> > + size_t suite_idx;
> > + const char *filter;
> > +
> > + struct clar_explicit *next;
> > +};
> > +
> > +struct clar_report {
> > + const char *test;
> > + int test_number;
> > + const char *suite;
> > +
> > + enum cl_test_status status;
> > + time_t start;
> > + double elapsed;
> > +
> > + struct clar_error *errors;
> > + struct clar_error *last_error;
> > +
> > + struct clar_report *next;
> > +};
> > +
> > +struct clar_summary {
> > + const char *filename;
> > + FILE *fp;
> > +};
> > +
> > +static struct {
> > + enum cl_test_status test_status;
> > +
> > + const char *active_test;
> > + const char *active_suite;
> > +
> > + int total_skipped;
> > + int total_errors;
> > +
> > + int tests_ran;
> > + int suites_ran;
> > +
> > + enum cl_output_format output_format;
> > +
> > + int report_errors_only;
> > + int exit_on_error;
> > + int verbosity;
> > +
> > + int write_summary;
> > + char *summary_filename;
> > + struct clar_summary *summary;
> > +
> > + struct clar_explicit *explicit;
> > + struct clar_explicit *last_explicit;
> > +
> > + struct clar_report *reports;
> > + struct clar_report *last_report;
> > +
> > + void (*local_cleanup)(void *);
> > + void *local_cleanup_payload;
> > +
> > + jmp_buf trampoline;
> > + int trampoline_enabled;
> > +
> > + cl_trace_cb *pfn_trace_cb;
> > + void *trace_payload;
> > +
> > +} _clar;
> > +
> > +struct clar_func {
> > + const char *name;
> > + void (*ptr)(void);
> > +};
> > +
> > +struct clar_suite {
> > + const char *name;
> > + struct clar_func initialize;
> > + struct clar_func cleanup;
> > + const struct clar_func *tests;
> > + size_t test_count;
> > + int enabled;
> > +};
> > +
> > +/* From clar_print_*.c */
> > +static void clar_print_init(int test_count, int suite_count, const char
> > *suite_names);
> > +static void clar_print_shutdown(int test_count, int suite_count, int
> > error_count);
> > +static void clar_print_error(int num, const struct clar_report *report,
> > const struct clar_error *error);
> > +static void clar_print_ontest(const char *suite_name, const char
> > *test_name, int test_number, enum cl_test_status failed);
> > +static void clar_print_onsuite(const char *suite_name, int suite_index);
> > +static void clar_print_onabort(const char *msg, ...);
> > +
> > +/* From clar_sandbox.c */
> > +static void clar_unsandbox(void);
> > +static int clar_sandbox(void);
> > +
> > +/* From summary.h */
> > +static struct clar_summary *clar_summary_init(const char *filename);
> > +static int clar_summary_shutdown(struct clar_summary *fp);
> > +
> > +/* Load the declarations for the test suite */
> > +#include "clar.suite"
> > +
> > +
> > +#define CL_TRACE(ev)
> > \
> > + do {
> > \
> > + if (_clar.pfn_trace_cb)
> > \
> > + _clar.pfn_trace_cb(ev,
> > \
> > + _clar.active_suite,
> > \
> > + _clar.active_test,
> > \
> > +
> > _clar.trace_payload); \
> > + } while (0)
> > +
> > +void cl_trace_register(cl_trace_cb *cb, void *payload)
> > +{
> > + _clar.pfn_trace_cb = cb;
> > + _clar.trace_payload = payload;
> > +}
> > +
> > +
> > +/* Core test functions */
> > +static void
> > +clar_report_errors(struct clar_report *report)
> > +{
> > + struct clar_error *error;
> > + int i = 1;
> > +
> > + for (error = report->errors; error; error = error->next)
> > + clar_print_error(i++, _clar.last_report, error);
> > +}
> > +
> > +static void
> > +clar_report_all(void)
> > +{
> > + struct clar_report *report;
> > + struct clar_error *error;
> > + int i = 1;
> > +
> > + for (report = _clar.reports; report; report = report->next) {
> > + if (report->status != CL_TEST_FAILURE)
> > + continue;
> > +
> > + for (error = report->errors; error; error = error->next)
> > + clar_print_error(i++, report, error);
> > + }
> > +}
> > +
> > +#ifdef WIN32
> > +# define clar_time DWORD
> > +
> > +static void clar_time_now(clar_time *out)
> > +{
> > + *out = GetTickCount();
> > +}
> > +
> > +static double clar_time_diff(clar_time *start, clar_time *end)
> > +{
> > + return ((double)*end - (double)*start) / 1000;
> > +}
> > +#else
> > +# include <sys/time.h>
> > +
> > +# define clar_time struct timeval
> > +
> > +static void clar_time_now(clar_time *out)
> > +{
> > + struct timezone tz;
> > +
> > + gettimeofday(out, &tz);
> > +}
> > +
> > +static double clar_time_diff(clar_time *start, clar_time *end)
> > +{
> > + return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
> > + ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
> > +}
> > +#endif
> > +
> > +static void
> > +clar_run_test(
> > + const struct clar_suite *suite,
> > + const struct clar_func *test,
> > + const struct clar_func *initialize,
> > + const struct clar_func *cleanup)
> > +{
> > + clar_time start, end;
> > +
> > + _clar.trampoline_enabled = 1;
> > +
> > + CL_TRACE(CL_TRACE__TEST__BEGIN);
> > +
> > + _clar.last_report->start = time(NULL);
> > + clar_time_now(&start);
> > +
> > + if (setjmp(_clar.trampoline) == 0) {
> > + if (initialize->ptr != NULL)
> > + initialize->ptr();
> > +
> > + CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
> > + test->ptr();
> > + CL_TRACE(CL_TRACE__TEST__RUN_END);
> > + }
> > +
> > + clar_time_now(&end);
> > +
> > + _clar.trampoline_enabled = 0;
> > +
> > + if (_clar.last_report->status == CL_TEST_NOTRUN)
> > + _clar.last_report->status = CL_TEST_OK;
> > +
> > + _clar.last_report->elapsed = clar_time_diff(&start, &end);
> > +
> > + if (_clar.local_cleanup != NULL)
> > + _clar.local_cleanup(_clar.local_cleanup_payload);
> > +
> > + if (cleanup->ptr != NULL)
> > + cleanup->ptr();
> > +
> > + CL_TRACE(CL_TRACE__TEST__END);
> > +
> > + _clar.tests_ran++;
> > +
> > + /* remove any local-set cleanup methods */
> > + _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);
> > + }
> > +}
> > +
> > +static void
> > +clar_run_suite(const struct clar_suite *suite, const char *filter)
> > +{
> > + const struct clar_func *test = suite->tests;
> > + size_t i, matchlen;
> > + struct clar_report *report;
> > + int exact = 0;
> > +
> > + if (!suite->enabled)
> > + return;
> > +
> > + if (_clar.exit_on_error && _clar.total_errors)
> > + return;
> > +
> > + if (!_clar.report_errors_only)
> > + clar_print_onsuite(suite->name, ++_clar.suites_ran);
> > +
> > + _clar.active_suite = suite->name;
> > + _clar.active_test = NULL;
> > + CL_TRACE(CL_TRACE__SUITE_BEGIN);
> > +
> > + if (filter) {
> > + size_t suitelen = strlen(suite->name);
> > + matchlen = strlen(filter);
> > + if (matchlen <= suitelen) {
> > + filter = NULL;
> > + } else {
> > + filter += suitelen;
> > + while (*filter == ':')
> > + ++filter;
> > + matchlen = strlen(filter);
> > +
> > + if (matchlen && filter[matchlen - 1] == '$') {
> > + exact = 1;
> > + matchlen--;
> > + }
> > + }
> > + }
> > +
> > + for (i = 0; i < suite->test_count; ++i) {
> > + if (filter && strncmp(test[i].name, filter, matchlen))
> > + continue;
> > +
> > + if (exact && strlen(test[i].name) != matchlen)
> > + continue;
> > +
> > + _clar.active_test = test[i].name;
> > +
> > + report = calloc(1, sizeof(struct clar_report));
> > + report->suite = _clar.active_suite;
> > + report->test = _clar.active_test;
> > + report->test_number = _clar.tests_ran;
> > + report->status = CL_TEST_NOTRUN;
> > +
> > + if (_clar.reports == NULL)
> > + _clar.reports = report;
> > +
> > + if (_clar.last_report != NULL)
> > + _clar.last_report->next = report;
> > +
> > + _clar.last_report = report;
> > +
> > + clar_run_test(suite, &test[i], &suite->initialize,
> > &suite->cleanup);
> > +
> > + if (_clar.exit_on_error && _clar.total_errors)
> > + return;
> > + }
> > +
> > + _clar.active_test = NULL;
> > + CL_TRACE(CL_TRACE__SUITE_END);
> > +}
> > +
> > +static void
> > +clar_usage(const char *arg)
> > +{
> > + printf("Usage: %s [options]\n\n", arg);
> > + printf("Options:\n");
> > + printf(" -sname Run only the suite with `name` (can go to
> > individual test name)\n");
> > + 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 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);
> > +}
> > +
> > +static void
> > +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) {
> > + clar_usage(argv[0]);
> > + }
> > + }
> > +
> > + for (i = 1; i < argc; ++i) {
> > + char *argument = argv[i];
> > +
> > + switch (argument[1]) {
> > + case 's':
> > + case 'i':
> > + case 'x': { /* given suite name */
> > + int offset = (argument[2] == '=') ? 3 : 2, found = 0;
> > + char action = argument[1];
> > + size_t j, arglen, suitelen, cmplen;
> > +
> > + argument += offset;
> > + arglen = strlen(argument);
> > +
> > + if (arglen == 0)
> > + clar_usage(argv[0]);
> > +
> > + for (j = 0; j < _clar_suite_count; ++j) {
> > + suitelen = strlen(_clar_suites[j].name);
> > + cmplen = (arglen < suitelen) ? arglen :
> > suitelen;
> > +
> > + if (strncmp(argument, _clar_suites[j].name,
> > cmplen) == 0) {
> > + int exact = (arglen >= suitelen);
> > +
> > + /* Do we have a real suite prefix
> > separated by a
> > + * trailing '::' or just a matching
> > substring? */
> > + if (arglen > suitelen &&
> > (argument[suitelen] != ':'
> > + || argument[suitelen + 1]
> > != ':'))
> > + continue;
> > +
> > + ++found;
> > +
> > + if (!exact)
> > + _clar.verbosity =
> > MAX(_clar.verbosity, 1);
> > +
> > + switch (action) {
> > + case 's': {
> > + struct clar_explicit *explicit
> > =
> > + calloc(1,
> > sizeof(struct clar_explicit));
> > + assert(explicit);
> > +
> > + explicit->suite_idx = j;
> > + explicit->filter = argument;
> > +
> > + if (_clar.explicit == NULL)
> > + _clar.explicit =
> > explicit;
> > +
> > + if (_clar.last_explicit !=
> > NULL)
> > +
> > _clar.last_explicit->next = explicit;
> > +
> > + _clar_suites[j].enabled = 1;
> > + _clar.last_explicit =
> > explicit;
> > + break;
> > + }
> > + case 'i': _clar_suites[j].enabled = 1;
> > break;
> > + case 'x': _clar_suites[j].enabled = 0;
> > break;
> > + }
> > +
> > + if (exact)
> > + break;
> > + }
> > + }
> > +
> > + if (!found) {
> > + clar_print_onabort("No suite matching '%s'
> > found.\n", argument);
> > + exit(-1);
> > + }
> > + break;
> > + }
> > +
> > + case 'q':
> > + _clar.report_errors_only = 1;
> > + break;
> > +
> > + case 'Q':
> > + _clar.exit_on_error = 1;
> > + break;
> > +
> > + case 't':
> > + _clar.output_format = CL_OUTPUT_TAP;
> > + break;
> > +
> > + case 'l': {
> > + size_t j;
> > + 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);
> > +
> > + exit(0);
> > + }
> > +
> > + case 'v':
> > + _clar.verbosity++;
> > + break;
> > +
> > + case 'r':
> > + _clar.write_summary = 1;
> > + free(_clar.summary_filename);
> > + _clar.summary_filename = *(argument + 2) ?
> > strdup(argument + 2) : NULL;
> > + break;
> > +
> > + default:
> > + assert(!"Unexpected commandline argument!");
> > + }
> > + }
> > +}
> > +
> > +void
> > +clar_test_init(int argc, char **argv)
> > +{
> > + const char *summary_env;
> > +
> > + if (argc > 1)
> > + clar_parse_args(argc, argv);
> > +
> > + clar_print_init(
> > + (int)_clar_callback_count,
> > + (int)_clar_suite_count,
> > + ""
> > + );
> > +
> > + if (!_clar.summary_filename &&
> > + (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
> > + _clar.write_summary = 1;
> > + _clar.summary_filename = strdup(summary_env);
> > + }
> > +
> > + if (_clar.write_summary && !_clar.summary_filename)
> > + _clar.summary_filename = strdup("summary.xml");
> > +
> > + if (_clar.write_summary &&
> > + !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
> > + clar_print_onabort("Failed to open the summary file\n");
> > + exit(-1);
> > + }
> > +
> > + if (clar_sandbox() < 0) {
> > + clar_print_onabort("Failed to sandbox the test runner.\n");
> > + exit(-1);
> > + }
> > +}
> > +
> > +int
> > +clar_test_run(void)
> > +{
> > + size_t i;
> > + struct clar_explicit *explicit;
> > +
> > + if (_clar.explicit) {
> > + for (explicit = _clar.explicit; explicit; explicit =
> > explicit->next)
> > + clar_run_suite(&_clar_suites[explicit->suite_idx],
> > explicit->filter);
> > + } else {
> > + for (i = 0; i < _clar_suite_count; ++i)
> > + clar_run_suite(&_clar_suites[i], NULL);
> > + }
> > +
> > + return _clar.total_errors;
> > +}
> > +
> > +void
> > +clar_test_shutdown(void)
> > +{
> > + struct clar_explicit *explicit, *explicit_next;
> > + struct clar_report *report, *report_next;
> > +
> > + clar_print_shutdown(
> > + _clar.tests_ran,
> > + (int)_clar_suite_count,
> > + _clar.total_errors
> > + );
> > +
> > + clar_unsandbox();
> > +
> > + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
> > + clar_print_onabort("Failed to write the summary file\n");
> > + exit(-1);
> > + }
> > +
> > + for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
> > + explicit_next = explicit->next;
> > + free(explicit);
> > + }
> > +
> > + for (report = _clar.reports; report; report = report_next) {
> > + report_next = report->next;
> > + free(report);
> > + }
> > +
> > + free(_clar.summary_filename);
> > +}
> > +
> > +int
> > +clar_test(int argc, char **argv)
> > +{
> > + int errors;
> > +
> > + clar_test_init(argc, argv);
> > + errors = clar_test_run();
> > + clar_test_shutdown();
> > +
> > + return errors;
> > +}
> > +
> > +static void abort_test(void)
> > +{
> > + if (!_clar.trampoline_enabled) {
> > + clar_print_onabort(
> > + "Fatal error: a cleanup method raised an
> > exception.");
> > + clar_report_errors(_clar.last_report);
> > + exit(-1);
> > + }
> > +
> > + CL_TRACE(CL_TRACE__TEST__LONGJMP);
> > + longjmp(_clar.trampoline, -1);
> > +}
> > +
> > +void clar__skip(void)
> > +{
> > + _clar.last_report->status = CL_TEST_SKIP;
> > + _clar.total_skipped++;
> > + abort_test();
> > +}
> > +
> > +void clar__fail(
> > + const char *file,
> > + const char *function,
> > + size_t line,
> > + const char *error_msg,
> > + const char *description,
> > + int should_abort)
> > +{
> > + struct clar_error *error = calloc(1, sizeof(struct clar_error));
> > +
> > + if (_clar.last_report->errors == NULL)
> > + _clar.last_report->errors = error;
> > +
> > + if (_clar.last_report->last_error != NULL)
> > + _clar.last_report->last_error->next = error;
> > +
> > + _clar.last_report->last_error = error;
> > +
> > + error->file = file;
> > + error->function = function;
> > + error->line_number = line;
> > + error->error_msg = error_msg;
> > +
> > + if (description != NULL)
> > + error->description = strdup(description);
> > +
> > + _clar.total_errors++;
> > + _clar.last_report->status = CL_TEST_FAILURE;
> > +
> > + if (should_abort)
> > + abort_test();
> > +}
> > +
> > +void clar__assert(
> > + int condition,
> > + const char *file,
> > + const char *function,
> > + size_t line,
> > + const char *error_msg,
> > + const char *description,
> > + int should_abort)
> > +{
> > + if (condition)
> > + return;
> > +
> > + clar__fail(file, function, line, error_msg, description,
> > should_abort);
> > +}
> > +
> > +void clar__assert_equal(
> > + const char *file,
> > + const char *function,
> > + size_t line,
> > + const char *err,
> > + int should_abort,
> > + const char *fmt,
> > + ...)
> > +{
> > + va_list args;
> > + char buf[4096];
> > + int is_equal = 1;
> > +
> > + va_start(args, fmt);
> > +
> > + if (!strcmp("%s", fmt)) {
> > + const char *s1 = va_arg(args, const char *);
> > + const char *s2 = va_arg(args, const char *);
> > + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
> > +
> > + if (!is_equal) {
> > + if (s1 && s2) {
> > + int pos;
> > + for (pos = 0; s1[pos] == s2[pos] && s1[pos] &&
> > s2[pos]; ++pos)
> > + /* find differing byte offset */;
> > + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at
> > byte %d)",
> > + s1, s2, pos);
> > + } else {
> > + p_snprintf(buf, sizeof(buf), "'%s' != '%s'",
> > s1, s2);
> > + }
> > + }
> > + }
> > + else if(!strcmp("%.*s", fmt)) {
> > + const char *s1 = va_arg(args, const char *);
> > + const char *s2 = va_arg(args, const char *);
> > + int len = va_arg(args, int);
> > + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
> > +
> > + if (!is_equal) {
> > + if (s1 && s2) {
> > + int pos;
> > + for (pos = 0; s1[pos] == s2[pos] && pos < len;
> > ++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);
> > + }
> > + }
> > + }
> > + else if (!strcmp("%ls", fmt)) {
> > + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
> > + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
> > + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1,
> > wcs2);
> > +
> > + if (!is_equal) {
> > + if (wcs1 && wcs2) {
> > + int pos;
> > + for (pos = 0; wcs1[pos] == wcs2[pos] &&
> > wcs1[pos] && wcs2[pos]; ++pos)
> > + /* find differing byte offset */;
> > + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'
> > (at byte %d)",
> > + wcs1, wcs2, pos);
> > + } else {
> > + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'",
> > wcs1, wcs2);
> > + }
> > + }
> > + }
> > + else if(!strcmp("%.*ls", fmt)) {
> > + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
> > + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
> > + int len = va_arg(args, int);
> > + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1,
> > wcs2, len);
> > +
> > + if (!is_equal) {
> > + if (wcs1 && wcs2) {
> > + int pos;
> > + for (pos = 0; wcs1[pos] == wcs2[pos] && pos <
> > len; ++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);
> > + }
> > + }
> > + }
> > + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
> > + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
> > + is_equal = (sz1 == sz2);
> > + if (!is_equal) {
> > + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
> > + strncat(buf, " != ", sizeof(buf) - offset);
> > + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4,
> > fmt, sz2);
> > + }
> > + }
> > + else if (!strcmp("%p", fmt)) {
> > + 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);
> > + }
> > + else {
> > + int i1 = va_arg(args, int), i2 = va_arg(args, int);
> > + is_equal = (i1 == i2);
> > + if (!is_equal) {
> > + int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
> > + strncat(buf, " != ", sizeof(buf) - offset);
> > + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4,
> > fmt, i2);
> > + }
> > + }
> > +
> > + va_end(args);
> > +
> > + if (!is_equal)
> > + clar__fail(file, function, line, err, buf, should_abort);
> > +}
> > +
> > +void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
> > +{
> > + _clar.local_cleanup = cleanup;
> > + _clar.local_cleanup_payload = opaque;
> > +}
> > +
> > +#include "clar/sandbox.h"
> > +#include "clar/fixtures.h"
> > +#include "clar/fs.h"
> > +#include "clar/print.h"
> > +#include "clar/summary.h"
> > diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
> > new file mode 100644
> > index 0000000000..8c22382bd5
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar.h
> > @@ -0,0 +1,173 @@
> > +/*
> > + * 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_H__
> > +#define __CLAR_TEST_H__
> > +
> > +#include <stdlib.h>
> > +
> > +enum cl_test_status {
> > + CL_TEST_OK,
> > + CL_TEST_FAILURE,
> > + CL_TEST_SKIP,
> > + CL_TEST_NOTRUN,
> > +};
> > +
> > +enum cl_output_format {
> > + CL_OUTPUT_CLAP,
> > + CL_OUTPUT_TAP,
> > +};
> > +
> > +/** Setup clar environment */
> > +void clar_test_init(int argc, char *argv[]);
> > +int clar_test_run(void);
> > +void clar_test_shutdown(void);
> > +
> > +/** One shot setup & run */
> > +int clar_test(int argc, char *argv[]);
> > +
> > +const char *clar_sandbox_path(void);
> > +
> > +void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
> > +void cl_fs_cleanup(void);
> > +
> > +/**
> > + * cl_trace_* is a hook to provide a simple global tracing
> > + * mechanism.
> > + *
> > + * The goal here is to let main() provide clar-proper
> > + * with a callback to optionally write log info for
> > + * test operations into the same stream used by their
> > + * actual tests. This would let them print test names
> > + * and maybe performance data as they choose.
> > + *
> > + * The goal is NOT to alter the flow of control or to
> > + * override test selection/skipping. (So the callback
> > + * does not return a value.)
> > + *
> > + * The goal is NOT to duplicate the existing
> > + * pass/fail/skip reporting. (So the callback
> > + * does not accept a status/errorcode argument.)
> > + *
> > + */
> > +typedef enum cl_trace_event {
> > + CL_TRACE__SUITE_BEGIN,
> > + CL_TRACE__SUITE_END,
> > + CL_TRACE__TEST__BEGIN,
> > + CL_TRACE__TEST__END,
> > + CL_TRACE__TEST__RUN_BEGIN,
> > + CL_TRACE__TEST__RUN_END,
> > + CL_TRACE__TEST__LONGJMP,
> > +} cl_trace_event;
> > +
> > +typedef void (cl_trace_cb)(
> > + cl_trace_event ev,
> > + const char *suite_name,
> > + const char *test_name,
> > + void *payload);
> > +
> > +/**
> > + * Register a callback into CLAR to send global trace events.
> > + * Pass NULL to disable.
> > + */
> > +void cl_trace_register(cl_trace_cb *cb, void *payload);
> > +
> > +
> > +#ifdef CLAR_FIXTURE_PATH
> > +const char *cl_fixture(const char *fixture_name);
> > +void cl_fixture_sandbox(const char *fixture_name);
> > +void cl_fixture_cleanup(const char *fixture_name);
> > +const char *cl_fixture_basename(const char *fixture_name);
> > +#endif
> > +
> > +/**
> > + * 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)
> > +
> > +/**
> > + * 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)
> > +
> > +/**
> > + * Assertion macros with no error message
> > + */
> > +#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
> > +#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
> > +#define cl_assert(expr) cl_assert_(expr, NULL)
> > +
> > +/**
> > + * Check macros with no error message
> > + */
> > +#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
> > +#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
> > +#define cl_check(expr) cl_check_(expr, NULL)
> > +
> > +/**
> > + * 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_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_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_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_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_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_b(b1,b2)
> > clar__assert_equal(__FILE__,__func__,__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))
> > +
> > +void clar__skip(void);
> > +
> > +void clar__fail(
> > + const char *file,
> > + const char *func,
> > + size_t line,
> > + const char *error,
> > + const char *description,
> > + int should_abort);
> > +
> > +void clar__assert(
> > + int condition,
> > + const char *file,
> > + const char *func,
> > + size_t line,
> > + const char *error,
> > + const char *description,
> > + int should_abort);
> > +
> > +void clar__assert_equal(
> > + const char *file,
> > + const char *func,
> > + size_t line,
> > + const char *err,
> > + int should_abort,
> > + const char *fmt,
> > + ...);
> > +
> > +#endif
> > diff --git a/t/unit-tests/clar/clar/fixtures.h
> > b/t/unit-tests/clar/clar/fixtures.h
> > new file mode 100644
> > index 0000000000..6ec6423484
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar/fixtures.h
> > @@ -0,0 +1,50 @@
> > +#ifdef CLAR_FIXTURE_PATH
> > +static const char *
> > +fixture_path(const char *base, const char *fixture_name)
> > +{
> > + static char _path[4096];
> > + size_t root_len;
> > +
> > + root_len = strlen(base);
> > + strncpy(_path, base, sizeof(_path));
> > +
> > + if (_path[root_len - 1] != '/')
> > + _path[root_len++] = '/';
> > +
> > + if (fixture_name[0] == '/')
> > + fixture_name++;
> > +
> > + strncpy(_path + root_len,
> > + fixture_name,
> > + sizeof(_path) - root_len);
> > +
> > + return _path;
> > +}
> > +
> > +const char *cl_fixture(const char *fixture_name)
> > +{
> > + return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
> > +}
> > +
> > +void cl_fixture_sandbox(const char *fixture_name)
> > +{
> > + fs_copy(cl_fixture(fixture_name), _clar_path);
> > +}
> > +
> > +const char *cl_fixture_basename(const char *fixture_name)
> > +{
> > + const char *p;
> > +
> > + for (p = fixture_name; *p; p++) {
> > + if (p[0] == '/' && p[1] && p[1] != '/')
> > + fixture_name = p+1;
> > + }
> > +
> > + return fixture_name;
> > +}
> > +
> > +void cl_fixture_cleanup(const char *fixture_name)
> > +{
> > + fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
> > +}
> > +#endif
> > diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
> > new file mode 100644
> > index 0000000000..3e39890bd3
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar/fs.h
> > @@ -0,0 +1,522 @@
> > +/*
> > + * By default, use a read/write loop to copy files on POSIX systems.
> > + * On Linux, use sendfile by default as it's slightly faster. On
> > + * macOS, we avoid fcopyfile by default because it's slightly slower.
> > + */
> > +#undef USE_FCOPYFILE
> > +#define USE_SENDFILE 1
> > +
> > +#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
> > +
> > +#ifdef __MINGW32__
> > +
> > +/* These security-enhanced functions are not available
> > + * in MinGW, so just use the vanilla ones */
> > +#define wcscpy_s(a, b, c) wcscpy((a), (c))
> > +#define wcscat_s(a, b, c) wcscat((a), (c))
> > +
> > +#endif /* __MINGW32__ */
> > +
> > +static int
> > +fs__dotordotdot(WCHAR *_tocheck)
> > +{
> > + return _tocheck[0] == '.' &&
> > + (_tocheck[1] == '\0' ||
> > + (_tocheck[1] == '.' && _tocheck[2] == '\0'));
> > +}
> > +
> > +static int
> > +fs_rmdir_rmdir(WCHAR *_wpath)
> > +{
> > + unsigned retries = 1;
> > +
> > + while (!RemoveDirectoryW(_wpath)) {
> > + /* Only retry when we have retries remaining, and the
> > + * error was ERROR_DIR_NOT_EMPTY. */
> > + if (retries++ > RM_RETRY_COUNT ||
> > + ERROR_DIR_NOT_EMPTY != GetLastError())
> > + return -1;
> > +
> > + /* Give whatever has a handle to a child item some time
> > + * to release it before trying again */
> > + Sleep(RM_RETRY_DELAY * retries * retries);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void translate_path(WCHAR *path, size_t path_size)
> > +{
> > + size_t path_len, i;
> > +
> > + if (wcsncmp(path, L"\\\\?\\", 4) == 0)
> > + return;
> > +
> > + path_len = wcslen(path);
> > + cl_assert(path_size > path_len + 4);
> > +
> > + for (i = path_len; i > 0; i--) {
> > + WCHAR c = path[i - 1];
> > +
> > + if (c == L'/')
> > + path[i + 3] = L'\\';
> > + else
> > + path[i + 3] = path[i - 1];
> > + }
> > +
> > + path[0] = L'\\';
> > + path[1] = L'\\';
> > + path[2] = L'?';
> > + path[3] = L'\\';
> > + path[path_len + 4] = L'\0';
> > +}
> > +
> > +static void
> > +fs_rmdir_helper(WCHAR *_wsource)
> > +{
> > + WCHAR buffer[CLAR_MAX_PATH];
> > + HANDLE find_handle;
> > + WIN32_FIND_DATAW find_data;
> > + size_t buffer_prefix_len;
> > +
> > + /* Set up the buffer and capture the length */
> > + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
> > + translate_path(buffer, CLAR_MAX_PATH);
> > + wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
> > + buffer_prefix_len = wcslen(buffer);
> > +
> > + /* FindFirstFile needs a wildcard to match multiple items */
> > + wcscat_s(buffer, CLAR_MAX_PATH, L"*");
> > + find_handle = FindFirstFileW(buffer, &find_data);
> > + cl_assert(INVALID_HANDLE_VALUE != find_handle);
> > +
> > + do {
> > + /* FindFirstFile/FindNextFile gives back . and ..
> > + * entries at the beginning */
> > + if (fs__dotordotdot(find_data.cFileName))
> > + continue;
> > +
> > + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH -
> > buffer_prefix_len, find_data.cFileName);
> > +
> > + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
> > + fs_rmdir_helper(buffer);
> > + else {
> > + /* If set, the +R bit must be cleared before deleting
> > */
> > + if (FILE_ATTRIBUTE_READONLY &
> > find_data.dwFileAttributes)
> > + cl_assert(SetFileAttributesW(buffer,
> > find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
> > +
> > + cl_assert(DeleteFileW(buffer));
> > + }
> > + }
> > + while (FindNextFileW(find_handle, &find_data));
> > +
> > + /* Ensure that we successfully completed the enumeration */
> > + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
> > +
> > + /* Close the find handle */
> > + FindClose(find_handle);
> > +
> > + /* Now that the directory is empty, remove it */
> > + cl_assert(0 == fs_rmdir_rmdir(_wsource));
> > +}
> > +
> > +static int
> > +fs_rm_wait(WCHAR *_wpath)
> > +{
> > + unsigned retries = 1;
> > + DWORD last_error;
> > +
> > + do {
> > + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
> > + last_error = GetLastError();
> > + else
> > + last_error = ERROR_SUCCESS;
> > +
> > + /* Is the item gone? */
> > + if (ERROR_FILE_NOT_FOUND == last_error ||
> > + ERROR_PATH_NOT_FOUND == last_error)
> > + return 0;
> > +
> > + Sleep(RM_RETRY_DELAY * retries * retries);
> > + }
> > + while (retries++ <= RM_RETRY_COUNT);
> > +
> > + return -1;
> > +}
> > +
> > +static void
> > +fs_rm(const char *_source)
> > +{
> > + WCHAR wsource[CLAR_MAX_PATH];
> > + DWORD attrs;
> > +
> > + /* The input path is UTF-8. Convert it to wide characters
> > + * for use with the Windows API */
> > + cl_assert(MultiByteToWideChar(CP_UTF8,
> > + MB_ERR_INVALID_CHARS,
> > + _source,
> > + -1, /* Indicates NULL termination */
> > + wsource,
> > + CLAR_MAX_PATH));
> > +
> > + translate_path(wsource, CLAR_MAX_PATH);
> > +
> > + /* Does the item exist? If not, we have no work to do */
> > + attrs = GetFileAttributesW(wsource);
> > +
> > + if (INVALID_FILE_ATTRIBUTES == attrs)
> > + return;
> > +
> > + if (FILE_ATTRIBUTE_DIRECTORY & attrs)
> > + fs_rmdir_helper(wsource);
> > + else {
> > + /* The item is a file. Strip the +R bit */
> > + if (FILE_ATTRIBUTE_READONLY & attrs)
> > + cl_assert(SetFileAttributesW(wsource, attrs &
> > ~FILE_ATTRIBUTE_READONLY));
> > +
> > + cl_assert(DeleteFileW(wsource));
> > + }
> > +
> > + /* Wait for the DeleteFile or RemoveDirectory call to complete */
> > + cl_assert(0 == fs_rm_wait(wsource));
> > +}
> > +
> > +static void
> > +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
> > +{
> > + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
> > + HANDLE find_handle;
> > + WIN32_FIND_DATAW find_data;
> > + size_t buf_source_prefix_len, buf_dest_prefix_len;
> > +
> > + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
> > + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
> > + translate_path(buf_source, CLAR_MAX_PATH);
> > + buf_source_prefix_len = wcslen(buf_source);
> > +
> > + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
> > + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
> > + translate_path(buf_dest, CLAR_MAX_PATH);
> > + buf_dest_prefix_len = wcslen(buf_dest);
> > +
> > + /* Get an enumerator for the items in the source. */
> > + wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
> > + find_handle = FindFirstFileW(buf_source, &find_data);
> > + cl_assert(INVALID_HANDLE_VALUE != find_handle);
> > +
> > + /* Create the target directory. */
> > + cl_assert(CreateDirectoryW(_wdest, NULL));
> > +
> > + do {
> > + /* FindFirstFile/FindNextFile gives back . and ..
> > + * entries at the beginning */
> > + if (fs__dotordotdot(find_data.cFileName))
> > + continue;
> > +
> > + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH -
> > buf_source_prefix_len, find_data.cFileName);
> > + wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH -
> > buf_dest_prefix_len, find_data.cFileName);
> > +
> > + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
> > + fs_copydir_helper(buf_source, buf_dest);
> > + else
> > + cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
> > + }
> > + while (FindNextFileW(find_handle, &find_data));
> > +
> > + /* Ensure that we successfully completed the enumeration */
> > + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
> > +
> > + /* Close the find handle */
> > + FindClose(find_handle);
> > +}
> > +
> > +static void
> > +fs_copy(const char *_source, const char *_dest)
> > +{
> > + WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
> > + DWORD source_attrs, dest_attrs;
> > + HANDLE find_handle;
> > + WIN32_FIND_DATAW find_data;
> > +
> > + /* The input paths are UTF-8. Convert them to wide characters
> > + * for use with the Windows API. */
> > + cl_assert(MultiByteToWideChar(CP_UTF8,
> > + MB_ERR_INVALID_CHARS,
> > + _source,
> > + -1,
> > + wsource,
> > + CLAR_MAX_PATH));
> > +
> > + cl_assert(MultiByteToWideChar(CP_UTF8,
> > + MB_ERR_INVALID_CHARS,
> > + _dest,
> > + -1,
> > + wdest,
> > + CLAR_MAX_PATH));
> > +
> > + translate_path(wsource, CLAR_MAX_PATH);
> > + translate_path(wdest, CLAR_MAX_PATH);
> > +
> > + /* Check the source for existence */
> > + source_attrs = GetFileAttributesW(wsource);
> > + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
> > +
> > + /* Check the target for existence */
> > + dest_attrs = GetFileAttributesW(wdest);
> > +
> > + if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
> > + /* Target exists; append last path part of source to target.
> > + * Use FindFirstFile to parse the path */
> > + find_handle = FindFirstFileW(wsource, &find_data);
> > + cl_assert(INVALID_HANDLE_VALUE != find_handle);
> > + wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
> > + wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
> > + FindClose(find_handle);
> > +
> > + /* Check the new target for existence */
> > + cl_assert(INVALID_FILE_ATTRIBUTES ==
> > GetFileAttributesW(wdest));
> > + }
> > +
> > + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
> > + fs_copydir_helper(wsource, wdest);
> > + else
> > + cl_assert(CopyFileW(wsource, wdest, TRUE));
> > +}
> > +
> > +void
> > +cl_fs_cleanup(void)
> > +{
> > +#ifdef CLAR_FIXTURE_PATH
> > + fs_rm(fixture_path(_clar_path, "*"));
> > +#endif
> > +}
> > +
> > +#else
> > +
> > +#include <errno.h>
> > +#include <string.h>
> > +#include <limits.h>
> > +#include <dirent.h>
> > +#include <fcntl.h>
> > +#include <unistd.h>
> > +#include <sys/types.h>
> > +#include <sys/stat.h>
> > +
> > +#if defined(__linux__)
> > +# include <sys/sendfile.h>
> > +#endif
> > +
> > +#if defined(__APPLE__)
> > +# include <copyfile.h>
> > +#endif
> > +
> > +static void basename_r(const char **out, int *out_len, const char *in)
> > +{
> > + size_t in_len = strlen(in), start_pos;
> > +
> > + for (in_len = strlen(in); in_len; in_len--) {
> > + if (in[in_len - 1] != '/')
> > + break;
> > + }
> > +
> > + for (start_pos = in_len; start_pos; start_pos--) {
> > + if (in[start_pos - 1] == '/')
> > + break;
> > + }
> > +
> > + cl_assert(in_len - start_pos < INT_MAX);
> > +
> > + if (in_len - start_pos > 0) {
> > + *out = &in[start_pos];
> > + *out_len = (in_len - start_pos);
> > + } else {
> > + *out = "/";
> > + *out_len = 1;
> > + }
> > +}
> > +
> > +static char *joinpath(const char *dir, const char *base, int base_len)
> > +{
> > + char *out;
> > + int len;
> > +
> > + if (base_len == -1) {
> > + size_t bl = strlen(base);
> > +
> > + cl_assert(bl < INT_MAX);
> > + base_len = (int)bl;
> > + }
> > +
> > + len = strlen(dir) + base_len + 2;
> > + cl_assert(len > 0);
> > +
> > + cl_assert(out = malloc(len));
> > + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
> > +
> > + return out;
> > +}
> > +
> > +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");
> > + while ((d = (errno = 0, readdir(source_dir))) != NULL) {
> > + char *child;
> > +
> > + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
> > + continue;
> > +
> > + child = joinpath(source, d->d_name, -1);
> > + fs_copy(child, dest);
> > + free(child);
> > + }
> > +
> > + cl_assert_(errno == 0, "Failed to iterate source dir");
> > +
> > + closedir(source_dir);
> > +}
> > +
> > +static void
> > +fs_copyfile_helper(const char *source, size_t source_len, const char *dest,
> > int dest_mode)
> > +{
> > + int in, out;
> > +
> > + cl_must_pass((in = open(source, O_RDONLY)));
> > + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
> > +
> > +#if USE_FCOPYFILE && defined(__APPLE__)
> > + ((void)(source_len)); /* unused */
> > + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
> > +#elif USE_SENDFILE && defined(__linux__)
> > + {
> > + ssize_t ret = 0;
> > +
> > + while (source_len && (ret = sendfile(out, in, NULL,
> > source_len)) > 0) {
> > + source_len -= (size_t)ret;
> > + }
> > + cl_assert(ret >= 0);
> > + }
> > +#else
> > + {
> > + char buf[131072];
> > + ssize_t ret;
> > +
> > + ((void)(source_len)); /* unused */
> > +
> > + while ((ret = read(in, buf, sizeof(buf))) > 0) {
> > + size_t len = (size_t)ret;
> > +
> > + while (len && (ret = write(out, buf, len)) > 0) {
> > + cl_assert(ret <= (ssize_t)len);
> > + len -= ret;
> > + }
> > + cl_assert(ret >= 0);
> > + }
> > + cl_assert(ret == 0);
> > + }
> > +#endif
> > +
> > + close(in);
> > + close(out);
> > +}
> > +
> > +static void
> > +fs_copy(const char *source, const char *_dest)
> > +{
> > + char *dbuf = NULL;
> > + const char *dest = NULL;
> > + struct stat source_st, dest_st;
> > +
> > + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy
> > source");
> > +
> > + if (lstat(_dest, &dest_st) == 0) {
> > + const char *base;
> > + int base_len;
> > +
> > + /* Target exists and is directory; append basename */
> > + cl_assert(S_ISDIR(dest_st.st_mode));
> > +
> > + basename_r(&base, &base_len, source);
> > + cl_assert(base_len < INT_MAX);
> > +
> > + dbuf = joinpath(_dest, base, base_len);
> > + dest = dbuf;
> > + } else if (errno != ENOENT) {
> > + cl_fail("Cannot copy; cannot stat destination");
> > + } else {
> > + dest = _dest;
> > + }
> > +
> > + if (S_ISDIR(source_st.st_mode)) {
> > + fs_copydir_helper(source, dest, source_st.st_mode);
> > + } else {
> > + fs_copyfile_helper(source, source_st.st_size, dest,
> > source_st.st_mode);
> > + }
> > +
> > + free(dbuf);
> > +}
> > +
> > +static void
> > +fs_rmdir_helper(const char *path)
> > +{
> > + DIR *dir;
> > + struct dirent *d;
> > +
> > + cl_assert_(dir = opendir(path), "Could not open dir");
> > + while ((d = (errno = 0, readdir(dir))) != NULL) {
> > + char *child;
> > +
> > + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
> > + continue;
> > +
> > + child = joinpath(path, d->d_name, -1);
> > + fs_rm(child);
> > + free(child);
> > + }
> > +
> > + cl_assert_(errno == 0, "Failed to iterate source dir");
> > + closedir(dir);
> > +
> > + cl_must_pass_(rmdir(path), "Could not remove directory");
> > +}
> > +
> > +static void
> > +fs_rm(const char *path)
> > +{
> > + struct stat st;
> > +
> > + if (lstat(path, &st)) {
> > + if (errno == ENOENT)
> > + return;
> > +
> > + cl_fail("Cannot copy; cannot stat destination");
> > + }
> > +
> > + if (S_ISDIR(st.st_mode)) {
> > + fs_rmdir_helper(path);
> > + } else {
> > + cl_must_pass(unlink(path));
> > + }
> > +}
> > +
> > +void
> > +cl_fs_cleanup(void)
> > +{
> > + clar_unsandbox();
> > + clar_sandbox();
> > +}
> > +#endif
> > diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
> > new file mode 100644
> > index 0000000000..c17e2f693b
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar/print.h
> > @@ -0,0 +1,211 @@
> > +/* clap: clar protocol, the traditional clar output format */
> > +
> > +static void clar_print_clap_init(int test_count, int suite_count, const
> > char *suite_names)
> > +{
> > + (void)test_count;
> > + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
> > + printf("Started (test status codes: OK='.' FAILURE='F'
> > SKIPPED='S')\n");
> > +}
> > +
> > +static void clar_print_clap_shutdown(int test_count, int suite_count, int
> > error_count)
> > +{
> > + (void)test_count;
> > + (void)suite_count;
> > + (void)error_count;
> > +
> > + printf("\n\n");
> > + clar_report_all();
> > +}
> > +
> > +static void clar_print_clap_error(int num, const struct clar_report
> > *report, const struct clar_error *error)
> > +{
> > + printf(" %d) Failure:\n", num);
> > +
> > + printf("%s::%s [%s:%"PRIuZ"]\n",
> > + report->suite,
> > + report->test,
> > + error->file,
> > + error->line_number);
> > +
> > + printf(" %s\n", error->error_msg);
> > +
> > + if (error->description != NULL)
> > + printf(" %s\n", error->description);
> > +
> > + printf("\n");
> > + fflush(stdout);
> > +}
> > +
> > +static void clar_print_clap_ontest(const char *suite_name, const char
> > *test_name, int test_number, enum cl_test_status status)
> > +{
> > + (void)test_name;
> > + (void)test_number;
> > +
> > + 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;
> > + }
> > + } else {
> > + switch (status) {
> > + case CL_TEST_OK: printf("."); break;
> > + case CL_TEST_FAILURE: printf("F"); break;
> > + case CL_TEST_SKIP: printf("S"); break;
> > + case CL_TEST_NOTRUN: printf("N"); break;
> > + }
> > +
> > + fflush(stdout);
> > + }
> > +}
> > +
> > +static void clar_print_clap_onsuite(const char *suite_name, int
> > suite_index)
> > +{
> > + if (_clar.verbosity == 1)
> > + printf("\n%s", suite_name);
> > +
> > + (void)suite_index;
> > +}
> > +
> > +static void clar_print_clap_onabort(const char *fmt, va_list arg)
> > +{
> > + vfprintf(stderr, fmt, arg);
> > +}
> > +
> > +/* tap: test anywhere protocol format */
> > +
> > +static void clar_print_tap_init(int test_count, int suite_count, const char
> > *suite_names)
> > +{
> > + (void)test_count;
> > + (void)suite_count;
> > + (void)suite_names;
> > + printf("TAP version 13\n");
> > +}
> > +
> > +static void clar_print_tap_shutdown(int test_count, int suite_count, int
> > error_count)
> > +{
> > + (void)suite_count;
> > + (void)error_count;
> > +
> > + printf("1..%d\n", test_count);
> > +}
> > +
> > +static void clar_print_tap_error(int num, const struct clar_report *report,
> > const struct clar_error *error)
> > +{
> > + (void)num;
> > + (void)report;
> > + (void)error;
> > +}
> > +
> > +static void print_escaped(const char *str)
> > +{
> > + char *c;
> > +
> > + while ((c = strchr(str, '\'')) != NULL) {
> > + printf("%.*s", (int)(c - str), str);
> > + printf("''");
> > + str = c + 1;
> > + }
> > +
> > + printf("%s", str);
> > +}
> > +
> > +static void clar_print_tap_ontest(const char *suite_name, const char
> > *test_name, int test_number, enum cl_test_status status)
> > +{
> > + const struct clar_error *error = _clar.last_report->errors;
> > +
> > + (void)test_name;
> > + (void)test_number;
> > +
> > + switch(status) {
> > + case CL_TEST_OK:
> > + printf("ok %d - %s::%s\n", test_number, suite_name,
> > test_name);
> > + break;
> > + 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 (error->description)
> > + printf(" %s\n", error->description);
> > +
> > + printf(" at:\n");
> > + printf(" file: '"); print_escaped(error->file);
> > printf("'\n");
> > + printf(" line: %" PRIuZ "\n", error->line_number);
> > + printf(" function: '%s'\n", error->function);
> > + printf(" ---\n");
> > +
> > + break;
> > + case CL_TEST_SKIP:
> > + case CL_TEST_NOTRUN:
> > + printf("ok %d - # SKIP %s::%s\n", test_number, suite_name,
> > test_name);
> > + break;
> > + }
> > +
> > + fflush(stdout);
> > +}
> > +
> > +static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
> > +{
> > + printf("# start of suite %d: %s\n", suite_index, suite_name);
> > +}
> > +
> > +static void clar_print_tap_onabort(const char *fmt, va_list arg)
> > +{
> > + printf("Bail out! ");
> > + vprintf(fmt, arg);
> > + fflush(stdout);
> > +}
> > +
> > +/* indirection between protocol output selection */
> > +
> > +#define PRINT(FN, ...) do { \
> > + switch (_clar.output_format) { \
> > + case CL_OUTPUT_CLAP: \
> > + clar_print_clap_##FN (__VA_ARGS__); \
> > + break; \
> > + case CL_OUTPUT_TAP: \
> > + clar_print_tap_##FN (__VA_ARGS__); \
> > + break; \
> > + default: \
> > + abort(); \
> > + } \
> > + } while (0)
> > +
> > +static void clar_print_init(int test_count, int suite_count, const char
> > *suite_names)
> > +{
> > + PRINT(init, test_count, suite_count, suite_names);
> > +}
> > +
> > +static void clar_print_shutdown(int test_count, int suite_count, int
> > error_count)
> > +{
> > + PRINT(shutdown, test_count, suite_count, error_count);
> > +}
> > +
> > +static void clar_print_error(int num, const struct clar_report *report,
> > const struct clar_error *error)
> > +{
> > + PRINT(error, num, report, error);
> > +}
> > +
> > +static void clar_print_ontest(const char *suite_name, const char
> > *test_name, int test_number, enum cl_test_status status)
> > +{
> > + PRINT(ontest, suite_name, test_name, test_number, status);
> > +}
> > +
> > +static void clar_print_onsuite(const char *suite_name, int suite_index)
> > +{
> > + PRINT(onsuite, suite_name, suite_index);
> > +}
> > +
> > +static void clar_print_onabort(const char *msg, ...)
> > +{
> > + va_list argp;
> > + va_start(argp, msg);
> > + PRINT(onabort, msg, argp);
> > + va_end(argp);
> > +}
> > diff --git a/t/unit-tests/clar/clar/sandbox.h
> > b/t/unit-tests/clar/clar/sandbox.h
> > new file mode 100644
> > index 0000000000..7c177f3525
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar/sandbox.h
> > @@ -0,0 +1,153 @@
> > +#ifdef __APPLE__
> > +#include <sys/syslimits.h>
> > +#endif
> > +
> > +static char _clar_path[4096 + 1];
> > +
> > +static int
> > +is_valid_tmp_path(const char *path)
> > +{
> > + STAT_T st;
> > +
> > + if (stat(path, &st) != 0)
> > + return 0;
> > +
> > + if (!S_ISDIR(st.st_mode))
> > + return 0;
> > +
> > + return (access(path, W_OK) == 0);
> > +}
> > +
> > +static int
> > +find_tmp_path(char *buffer, size_t length)
> > +{
> > +#ifndef _WIN32
> > + static const size_t var_count = 5;
> > + static const char *env_vars[] = {
> > + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
> > + };
> > +
> > + size_t i;
> > +
> > + 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;
> > + }
> > + }
> > +
> > + /* 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)
> > + return 0;
> > +
> > + if (GetTempPath((DWORD)length, buffer))
> > + return 0;
> > +#endif
> > +
> > + /* This system doesn't like us, try to use the current directory */
> > + if (is_valid_tmp_path(".")) {
> > + strncpy(buffer, ".", length - 1);
> > + buffer[length - 1] = '\0';
> > + return 0;
> > + }
> > +
> > + return -1;
> > +}
> > +
> > +static void clar_unsandbox(void)
> > +{
> > + if (_clar_path[0] == '\0')
> > + return;
> > +
> > + cl_must_pass(chdir(".."));
> > +
> > + fs_rm(_clar_path);
> > +}
> > +
> > +static int build_sandbox_path(void)
> > +{
> > +#ifdef CLAR_TMPDIR
> > + const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
> > +#else
> > + const char path_tail[] = "clar_tmp_XXXXXX";
> > +#endif
> > +
> > + size_t len;
> > +
> > + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
> > + return -1;
> > +
> > + len = strlen(_clar_path);
> > +
> > +#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 (_clar_path[len - 1] != '/') {
> > + _clar_path[len++] = '/';
> > + }
> > +
> > + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
> > +
> > +#if defined(__MINGW32__)
> > + if (_mktemp(_clar_path) == NULL)
> > + return -1;
> > +
> > + if (mkdir(_clar_path, 0700) != 0)
> > + return -1;
> > +#elif defined(_WIN32)
> > + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
> > + return -1;
> > +
> > + if (mkdir(_clar_path, 0700) != 0)
> > + return -1;
> > +#else
> > + if (mkdtemp(_clar_path) == NULL)
> > + return -1;
> > +#endif
> > +
> > + return 0;
> > +}
> > +
> > +static int clar_sandbox(void)
> > +{
> > + if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
> > + return -1;
> > +
> > + if (chdir(_clar_path) != 0)
> > + return -1;
> > +
> > + return 0;
> > +}
> > +
> > +const char *clar_sandbox_path(void)
> > +{
> > + return _clar_path;
> > +}
> > diff --git a/t/unit-tests/clar/clar/summary.h
> > b/t/unit-tests/clar/clar/summary.h
> > new file mode 100644
> > index 0000000000..4dd352e28b
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar/summary.h
> > @@ -0,0 +1,143 @@
> > +
> > +#include <stdio.h>
> > +#include <time.h>
> > +
> > +static int clar_summary_close_tag(
> > + struct clar_summary *summary, const char *tag, int indent)
> > +{
> > + const char *indt;
> > +
> > + if (indent == 0) indt = "";
> > + else if (indent == 1) indt = "\t";
> > + else indt = "\t\t";
> > +
> > + return fprintf(summary->fp, "%s</%s>\n", indt, tag);
> > +}
> > +
> > +static int clar_summary_testsuites(struct clar_summary *summary)
> > +{
> > + return fprintf(summary->fp, "<testsuites>\n");
> > +}
> > +
> > +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(×tamp);
> > + char iso_dt[20];
> > +
> > + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
> > + return -1;
> > +
> > + return fprintf(summary->fp, "\t<testsuite"
> > + " id=\"%d\""
> > + " name=\"%s\""
> > + " hostname=\"localhost\""
> > + " timestamp=\"%s\""
> > + " tests=\"%d\""
> > + " failures=\"%d\""
> > + " errors=\"%d\">\n",
> > + idn, name, iso_dt, test_count, fail_count,
> > error_count);
> > +}
> > +
> > +static int clar_summary_testcase(struct clar_summary *summary,
> > + const char *name, const char *classname, double elapsed)
> > +{
> > + return fprintf(summary->fp,
> > + "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
> > + name, classname, elapsed);
> > +}
> > +
> > +static int clar_summary_failure(struct clar_summary *summary,
> > + const char *type, const char *message, const char *desc)
> > +{
> > + return fprintf(summary->fp,
> > + "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
> > + type, message, desc);
> > +}
> > +
> > +static int clar_summary_skipped(struct clar_summary *summary)
> > +{
> > + return fprintf(summary->fp, "\t\t\t<skipped />\n");
> > +}
> > +
> > +struct clar_summary *clar_summary_init(const char *filename)
> > +{
> > + struct clar_summary *summary;
> > + FILE *fp;
> > +
> > + if ((fp = fopen(filename, "w")) == NULL) {
> > + perror("fopen");
> > + return NULL;
> > + }
> > +
> > + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
> > + perror("malloc");
> > + fclose(fp);
> > + return NULL;
> > + }
> > +
> > + summary->filename = filename;
> > + summary->fp = fp;
> > +
> > + return summary;
> > +}
> > +
> > +int clar_summary_shutdown(struct clar_summary *summary)
> > +{
> > + struct clar_report *report;
> > + const char *last_suite = NULL;
> > +
> > + if (clar_summary_testsuites(summary) < 0)
> > + goto on_error;
> > +
> > + report = _clar.reports;
> > + while (report != NULL) {
> > + struct clar_error *error = report->errors;
> > +
> > + if (last_suite == NULL || strcmp(last_suite, report->suite) !=
> > 0) {
> > + if (clar_summary_testsuite(summary, 0, report->suite,
> > + report->start, _clar.tests_ran,
> > _clar.total_errors, 0) < 0)
> > + goto on_error;
> > + }
> > +
> > + last_suite = report->suite;
> > +
> > + clar_summary_testcase(summary, report->test, report->suite,
> > report->elapsed);
> > +
> > + while (error != NULL) {
> > + if (clar_summary_failure(summary, "assert",
> > + error->error_msg, error->description) < 0)
> > + goto on_error;
> > +
> > + error = error->next;
> > + }
> > +
> > + if (report->status == CL_TEST_SKIP)
> > + clar_summary_skipped(summary);
> > +
> > + if (clar_summary_close_tag(summary, "testcase", 2) < 0)
> > + goto on_error;
> > +
> > + report = report->next;
> > +
> > + if (!report || strcmp(last_suite, report->suite) != 0) {
> > + if (clar_summary_close_tag(summary, "testsuite", 1) <
> > 0)
> > + goto on_error;
> > + }
> > + }
> > +
> > + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
> > + fclose(summary->fp) != 0)
> > + goto on_error;
> > +
> > + printf("written summary file to %s\n", summary->filename);
> > +
> > + free(summary);
> > + return 0;
> > +
> > +on_error:
> > + fclose(summary->fp);
> > + free(summary);
> > + return -1;
> > +}
> > diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
> > new file mode 100755
> > index 0000000000..80996ac3e7
> > --- /dev/null
> > +++ b/t/unit-tests/clar/generate.py
> > @@ -0,0 +1,266 @@
> > +#!/usr/bin/env python
> > +#
> > +# 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.
> > +#
> > +
> > +from __future__ import with_statement
> > +from string import Template
> > +import re, fnmatch, os, sys, codecs, pickle
> > +
> > +class Module(object):
> > + class Template(object):
> > + def __init__(self, module):
> > + self.module = module
> > +
> > + def _render_callback(self, cb):
> > + if not cb:
> > + return ' { NULL, NULL }'
> > + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
> > +
> > + class DeclarationTemplate(Template):
> > + def render(self):
> > + out = "\n".join("extern %s;" % cb['declaration'] for cb in
> > self.module.callbacks) + "\n"
> > +
> > + for initializer in self.module.initializers:
> > + out += "extern %s;\n" % initializer['declaration']
> > +
> > + if self.module.cleanup:
> > + out += "extern %s;\n" % self.module.cleanup['declaration']
> > +
> > + return out
> > +
> > + class CallbacksTemplate(Template):
> > + def render(self):
> > + out = "static const struct clar_func _clar_cb_%s[] = {\n" %
> > self.module.name
> > + out += ",\n".join(self._render_callback(cb) for cb in
> > self.module.callbacks)
> > + out += "\n};\n"
> > + return out
> > +
> > + class InfoTemplate(Template):
> > + def render(self):
> > + templates = []
> > +
> > + initializers = self.module.initializers
> > + if len(initializers) == 0:
> > + initializers = [ None ]
> > +
> > + for initializer in initializers:
> > + name = self.module.clean_name()
> > + if initializer and
> > initializer['short_name'].startswith('initialize_'):
> > + variant =
> > initializer['short_name'][len('initialize_'):]
> > + name += " (%s)" % variant.replace('_', ' ')
> > +
> > + template = Template(
> > + r"""
> > + {
> > + "${clean_name}",
> > + ${initialize},
> > + ${cleanup},
> > + ${cb_ptr}, ${cb_count}, ${enabled}
> > + }"""
> > + ).substitute(
> > + clean_name = name,
> > + initialize = self._render_callback(initializer),
> > + cleanup = self._render_callback(self.module.cleanup),
> > + cb_ptr = "_clar_cb_%s" % self.module.name,
> > + cb_count = len(self.module.callbacks),
> > + enabled = int(self.module.enabled)
> > + )
> > + templates.append(template)
> > +
> > + return ','.join(templates)
> > +
> > + def __init__(self, name):
> > + self.name = name
> > +
> > + self.mtime = None
> > + self.enabled = True
> > + self.modified = False
> > +
> > + def clean_name(self):
> > + return self.name.replace("_", "::")
> > +
> > + def _skip_comments(self, text):
> > + SKIP_COMMENTS_REGEX = re.compile(
> > + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
> > + re.DOTALL | re.MULTILINE)
> > +
> > + def _replacer(match):
> > + s = match.group(0)
> > + return "" if s.startswith('/') else s
> > +
> > + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
> > +
> > + def parse(self, contents):
> > + TEST_FUNC_REGEX =
> > r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
> > +
> > + contents = self._skip_comments(contents)
> > + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
> > +
> > + self.callbacks = []
> > + self.initializers = []
> > + self.cleanup = None
> > +
> > + for (declaration, symbol, short_name) in regex.findall(contents):
> > + data = {
> > + "short_name" : short_name,
> > + "declaration" : declaration,
> > + "symbol" : symbol
> > + }
> > +
> > + if short_name.startswith('initialize'):
> > + self.initializers.append(data)
> > + elif short_name == 'cleanup':
> > + self.cleanup = data
> > + else:
> > + self.callbacks.append(data)
> > +
> > + return self.callbacks != []
> > +
> > + def refresh(self, path):
> > + self.modified = False
> > +
> > + try:
> > + st = os.stat(path)
> > +
> > + # Not modified
> > + if st.st_mtime == self.mtime:
> > + return True
> > +
> > + self.modified = True
> > + self.mtime = st.st_mtime
> > +
> > + with codecs.open(path, encoding='utf-8') as fp:
> > + raw_content = fp.read()
> > +
> > + except IOError:
> > + return False
> > +
> > + return self.parse(raw_content)
> > +
> > +class TestSuite(object):
> > +
> > + def __init__(self, path, output):
> > + self.path = path
> > + self.output = output
> > +
> > + def should_generate(self, path):
> > + if not os.path.isfile(path):
> > + return True
> > +
> > + if any(module.modified for module in self.modules.values()):
> > + return True
> > +
> > + return False
> > +
> > + def find_modules(self):
> > + modules = []
> > + for root, _, files in os.walk(self.path):
> > + module_root = root[len(self.path):]
> > + module_root = [c for c in module_root.split(os.sep) if c]
> > +
> > + tests_in_module = fnmatch.filter(files, "*.c")
> > +
> > + for test_file in tests_in_module:
> > + full_path = os.path.join(root, test_file)
> > + module_name = "_".join(module_root +
> > [test_file[:-2]]).replace("-", "_")
> > +
> > + modules.append((full_path, module_name))
> > +
> > + return modules
> > +
> > + def load_cache(self):
> > + path = os.path.join(self.output, '.clarcache')
> > + cache = {}
> > +
> > + try:
> > + fp = open(path, 'rb')
> > + cache = pickle.load(fp)
> > + fp.close()
> > + except (IOError, ValueError):
> > + pass
> > +
> > + return cache
> > +
> > + def save_cache(self):
> > + path = os.path.join(self.output, '.clarcache')
> > + with open(path, 'wb') as cache:
> > + pickle.dump(self.modules, cache)
> > +
> > + def load(self, force = False):
> > + module_data = self.find_modules()
> > + self.modules = {} if force else self.load_cache()
> > +
> > + for path, name in module_data:
> > + if name not in self.modules:
> > + self.modules[name] = Module(name)
> > +
> > + if not self.modules[name].refresh(path):
> > + del self.modules[name]
> > +
> > + def disable(self, excluded):
> > + for exclude in excluded:
> > + for module in self.modules.values():
> > + name = module.clean_name()
> > + if name.startswith(exclude):
> > + module.enabled = False
> > + module.modified = True
> > +
> > + def suite_count(self):
> > + return sum(max(1, len(m.initializers)) for m in
> > self.modules.values())
> > +
> > + def callback_count(self):
> > + return sum(len(module.callbacks) for module in
> > self.modules.values())
> > +
> > + def write(self):
> > + output = os.path.join(self.output, 'clar.suite')
> > +
> > + if not self.should_generate(output):
> > + return False
> > +
> > + with open(output, 'w') as data:
> > + modules = sorted(self.modules.values(), key=lambda module:
> > module.name)
> > +
> > + for module in modules:
> > + t = Module.DeclarationTemplate(module)
> > + data.write(t.render())
> > +
> > + for module in modules:
> > + t = Module.CallbacksTemplate(module)
> > + data.write(t.render())
> > +
> > + suites = "static struct clar_suite _clar_suites[] = {" +
> > ','.join(
> > + Module.InfoTemplate(module).render() for module in modules
> > + ) + "\n};\n"
> > +
> > + data.write(suites)
> > +
> > + data.write("static const size_t _clar_suite_count = %d;\n" %
> > self.suite_count())
> > + data.write("static const size_t _clar_callback_count = %d;\n" %
> > self.callback_count())
> > +
> > + self.save_cache()
> > + return True
> > +
> > +if __name__ == '__main__':
> > + from optparse import OptionParser
> > +
> > + parser = OptionParser()
> > + parser.add_option('-f', '--force', action="store_true", dest='force',
> > default=False)
> > + parser.add_option('-x', '--exclude', dest='excluded', action='append',
> > default=[])
> > + parser.add_option('-o', '--output', dest='output')
> > +
> > + options, args = parser.parse_args()
> > + if len(args) > 1:
> > + print("More than one path given")
> > + sys.exit(1)
> > +
> > + path = args.pop() if args else '.'
> > + output = options.output or path
> > + suite = TestSuite(path, output)
> > + suite.load(options.force)
> > + suite.disable(options.excluded)
> > + if suite.write():
> > + print("Written `clar.suite` (%d tests in %d suites)" %
> > (suite.callback_count(), suite.suite_count()))
> > diff --git a/t/unit-tests/clar/test/.gitignore
> > b/t/unit-tests/clar/test/.gitignore
> > new file mode 100644
> > index 0000000000..a477d0c40c
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/.gitignore
> > @@ -0,0 +1,4 @@
> > +clar.suite
> > +.clarcache
> > +clar_test
> > +*.o
> > diff --git a/t/unit-tests/clar/test/Makefile
> > b/t/unit-tests/clar/test/Makefile
> > new file mode 100644
> > index 0000000000..93c6b2ad32
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/Makefile
> > @@ -0,0 +1,39 @@
> > +#
> > +# 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.
> > +#
> > +
> > +#
> > +# Set up the path to the clar sources and to the fixtures directory
> > +#
> > +# The fixture path needs to be an absolute path so it can be used
> > +# even after we have chdir'ed into the test directory while testing.
> > +#
> > +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
> > +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
> > +CLAR_PATH := $(dir $(TEST_DIRECTORY))
> > +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
> > +
> > +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
> > +
> > +.PHONY: clean
> > +
> > +# list the objects that go into our test
> > +objects = main.o sample.o
> > +
> > +# build the test executable itself
> > +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
> > + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
> > +
> > +# test object files depend on clar macros
> > +$(objects) : $(CLAR_PATH)clar.h
> > +
> > +# build the clar.suite file of test metadata
> > +clar.suite:
> > + python "$(CLAR_PATH)generate.py" .
> > +
> > +# remove all generated files
> > +clean:
> > + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
> > diff --git a/t/unit-tests/clar/test/clar_test.h
> > b/t/unit-tests/clar/test/clar_test.h
> > new file mode 100644
> > index 0000000000..0fcaa639aa
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/clar_test.h
> > @@ -0,0 +1,16 @@
> > +/*
> > + * 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/main.c b/t/unit-tests/clar/test/main.c
> > new file mode 100644
> > index 0000000000..59e56ad255
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/main.c
> > @@ -0,0 +1,40 @@
> > +/*
> > + * 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_test.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;
> > +
> > +#ifdef _WIN32
> > +int __cdecl main(int argc, char *argv[])
> > +#else
> > +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);
> > +
> > + /* Your custom cleanup here */
> > + cl_assert_equal_i(8, global_test_counter);
> > +
> > + return ret;
> > +}
> > diff --git a/t/unit-tests/clar/test/main.c.sample
> > b/t/unit-tests/clar/test/main.c.sample
> > new file mode 100644
> > index 0000000000..a4d91b72fa
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/main.c.sample
> > @@ -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_test.h"
> > +
> > +/*
> > + * Minimal main() for clar tests.
> > + *
> > + * Modify this with any application specific setup or teardown that you
> > need.
> > + * 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, 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
> > +{
> > + /* Run the test suite */
> > + return clar_test(argc, argv);
> > +}
> > diff --git a/t/unit-tests/clar/test/resources/test/file
> > b/t/unit-tests/clar/test/resources/test/file
> > new file mode 100644
> > index 0000000000..220f4aa98a
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/resources/test/file
> > @@ -0,0 +1 @@
> > +File
> > diff --git a/t/unit-tests/clar/test/sample.c
> > b/t/unit-tests/clar/test/sample.c
> > new file mode 100644
> > index 0000000000..faa1209262
> > --- /dev/null
> > +++ b/t/unit-tests/clar/test/sample.c
> > @@ -0,0 +1,84 @@
> > +#include "clar_test.h"
> > +#include <sys/stat.h>
> > +
> > +static int file_size(const char *filename)
> > +{
> > + struct stat st;
> > +
> > + if (stat(filename, &st) == 0)
> > + return (int)st.st_size;
> > + return -1;
> > +}
> > +
> > +void test_sample__initialize(void)
> > +{
> > + global_test_counter++;
> > +}
> > +
> > +void test_sample__cleanup(void)
> > +{
> > + cl_fixture_cleanup("test");
> > +
> > + cl_assert(file_size("test/file") == -1);
> > +}
> > +
> > +void test_sample__1(void)
> > +{
> > + cl_assert(1);
> > + cl_must_pass(0); /* 0 == success */
> > + cl_must_fail(-1); /* <0 == failure */
> > + cl_must_pass(-1); /* demonstrate a failing call */
> > +}
> > +
> > +void test_sample__2(void)
> > +{
> > + cl_fixture_sandbox("test");
> > +
> > + cl_assert(file_size("test/nonexistent") == -1);
> > + cl_assert(file_size("test/file") > 0);
> > + cl_assert(100 == 101);
> > +}
> > +
> > +void test_sample__strings(void)
> > +{
> > + const char *actual = "expected";
> > + cl_assert_equal_s("expected", actual);
> > + cl_assert_equal_s_("expected", actual, "second try with annotation");
> > + cl_assert_equal_s_("mismatched", actual, "this one fails");
> > +}
> > +
> > +void test_sample__strings_with_length(void)
> > +{
> > + const char *actual = "expected";
> > + cl_assert_equal_strn("expected_", actual, 8);
> > + cl_assert_equal_strn("exactly", actual, 2);
> > + cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
> > + cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
> > +}
> > +
> > +void test_sample__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)
> > +{
> > + int value = 100;
> > + cl_assert_equal_i_fmt(022, value, "%04o");
> > +}
> > +
> > +void test_sample__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)
> > +{
> > + const char *actual = "expected";
> > + cl_assert_equal_p(actual, actual); /* pointers to same object */
> > + cl_assert_equal_p(&actual, actual);
> > +}
>
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-23 12:16 ` Johannes Schindelin
@ 2024-08-28 13:20 ` Phillip Wood
0 siblings, 0 replies; 172+ messages in thread
From: Phillip Wood @ 2024-08-28 13:20 UTC (permalink / raw)
To: Johannes Schindelin, Phillip Wood
Cc: Patrick Steinhardt, git, René Scharfe, Junio C Hamano,
Kyle Lippincott, Josh Steadmon, rsbecker, Edward Thomson
Hi Dscho
On 23/08/2024 13:16, Johannes Schindelin wrote:
> Hi Phillip,
>
> thank you for looping me in; I meant to answer earlier.
>
> I am very, very happy to see that there is a chance to adopt a test
> framework like `clar`. Therefore, I am very interested in assisting in
> every which way I can.
That's great, I like the basic ideas in clar but I think the assertions
could use some improvement.
Best Wishes
Phillip
> On Fri, 16 Aug 2024, Phillip Wood wrote:
>
>> Johannes - are these format specifiers supported by the version of MSVCRT that
>> is used by git for windows? c.f.
>> https://lore.kernel.org/git/nycvar.QRO.7.76.6.2203241456250.388@tvgsbejvaqbjf.bet/
>
> The format specifiers _should_ work well both with GCC and Visual C. What
> did not work well was using `struct stat` with `_stat()` (note that the
> latter has a leading underscore and the former does not).
>
> There were other issues, top of which was the bit tricky part to adapt the
> `CMakeLists.txt` definition, in particular the part where `clar.suite` is
> generated. But I managed to squeeze in that work somehow and Patrick was
> kind enough to already integrate the patches.
>
> Ciao,
> Johannes
>
>> On 16/08/2024 08:04, Patrick Steinhardt wrote:
>>> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
>>> [...]
>>> +#ifdef _WIN32
>>> [...]
>>> +# ifndef PRIuZ
>>> +# define PRIuZ "Iu"
>>> +# endif
>>> +# ifndef PRIxZ
>>> +# define PRIxZ "Ix"
>>> +# endif
>>
>> Thanks
>>
>> Phillip
>>
>>> +# if defined(_MSC_VER) || defined(__MINGW32__)
>>> + typedef struct stat STAT_T;
>>> +# else
>>> + typedef struct _stat STAT_T;
>>> +# endif
>>> +#else
>>> +# include <sys/wait.h> /* waitpid(2) */
>>> +# include <unistd.h>
>>> +# define _MAIN_CC
>>> +# define p_snprintf snprintf
>>> +# ifndef PRIuZ
>>> +# define PRIuZ "zu"
>>> +# endif
>>> +# ifndef PRIxZ
>>> +# define PRIxZ "zx"
>>> +# endif
>>> + typedef struct stat STAT_T;
>>> +#endif
>>> +
>>> +#define MAX(x, y) (((x) > (y)) ? (x) : (y))
>>> +
>>> +#include "clar.h"
>>> +
>>> +static void fs_rm(const char *_source);
>>> +static void fs_copy(const char *_source, const char *dest);
>>> +
>>> +#ifdef CLAR_FIXTURE_PATH
>>> +static const char *
>>> +fixture_path(const char *base, const char *fixture_name);
>>> +#endif
>>> +
>>> +struct clar_error {
>>> + const char *file;
>>> + const char *function;
>>> + size_t line_number;
>>> + const char *error_msg;
>>> + char *description;
>>> +
>>> + struct clar_error *next;
>>> +};
>>> +
>>> +struct clar_explicit {
>>> + size_t suite_idx;
>>> + const char *filter;
>>> +
>>> + struct clar_explicit *next;
>>> +};
>>> +
>>> +struct clar_report {
>>> + const char *test;
>>> + int test_number;
>>> + const char *suite;
>>> +
>>> + enum cl_test_status status;
>>> + time_t start;
>>> + double elapsed;
>>> +
>>> + struct clar_error *errors;
>>> + struct clar_error *last_error;
>>> +
>>> + struct clar_report *next;
>>> +};
>>> +
>>> +struct clar_summary {
>>> + const char *filename;
>>> + FILE *fp;
>>> +};
>>> +
>>> +static struct {
>>> + enum cl_test_status test_status;
>>> +
>>> + const char *active_test;
>>> + const char *active_suite;
>>> +
>>> + int total_skipped;
>>> + int total_errors;
>>> +
>>> + int tests_ran;
>>> + int suites_ran;
>>> +
>>> + enum cl_output_format output_format;
>>> +
>>> + int report_errors_only;
>>> + int exit_on_error;
>>> + int verbosity;
>>> +
>>> + int write_summary;
>>> + char *summary_filename;
>>> + struct clar_summary *summary;
>>> +
>>> + struct clar_explicit *explicit;
>>> + struct clar_explicit *last_explicit;
>>> +
>>> + struct clar_report *reports;
>>> + struct clar_report *last_report;
>>> +
>>> + void (*local_cleanup)(void *);
>>> + void *local_cleanup_payload;
>>> +
>>> + jmp_buf trampoline;
>>> + int trampoline_enabled;
>>> +
>>> + cl_trace_cb *pfn_trace_cb;
>>> + void *trace_payload;
>>> +
>>> +} _clar;
>>> +
>>> +struct clar_func {
>>> + const char *name;
>>> + void (*ptr)(void);
>>> +};
>>> +
>>> +struct clar_suite {
>>> + const char *name;
>>> + struct clar_func initialize;
>>> + struct clar_func cleanup;
>>> + const struct clar_func *tests;
>>> + size_t test_count;
>>> + int enabled;
>>> +};
>>> +
>>> +/* From clar_print_*.c */
>>> +static void clar_print_init(int test_count, int suite_count, const char
>>> *suite_names);
>>> +static void clar_print_shutdown(int test_count, int suite_count, int
>>> error_count);
>>> +static void clar_print_error(int num, const struct clar_report *report,
>>> const struct clar_error *error);
>>> +static void clar_print_ontest(const char *suite_name, const char
>>> *test_name, int test_number, enum cl_test_status failed);
>>> +static void clar_print_onsuite(const char *suite_name, int suite_index);
>>> +static void clar_print_onabort(const char *msg, ...);
>>> +
>>> +/* From clar_sandbox.c */
>>> +static void clar_unsandbox(void);
>>> +static int clar_sandbox(void);
>>> +
>>> +/* From summary.h */
>>> +static struct clar_summary *clar_summary_init(const char *filename);
>>> +static int clar_summary_shutdown(struct clar_summary *fp);
>>> +
>>> +/* Load the declarations for the test suite */
>>> +#include "clar.suite"
>>> +
>>> +
>>> +#define CL_TRACE(ev)
>>> \
>>> + do {
>>> \
>>> + if (_clar.pfn_trace_cb)
>>> \
>>> + _clar.pfn_trace_cb(ev,
>>> \
>>> + _clar.active_suite,
>>> \
>>> + _clar.active_test,
>>> \
>>> +
>>> _clar.trace_payload); \
>>> + } while (0)
>>> +
>>> +void cl_trace_register(cl_trace_cb *cb, void *payload)
>>> +{
>>> + _clar.pfn_trace_cb = cb;
>>> + _clar.trace_payload = payload;
>>> +}
>>> +
>>> +
>>> +/* Core test functions */
>>> +static void
>>> +clar_report_errors(struct clar_report *report)
>>> +{
>>> + struct clar_error *error;
>>> + int i = 1;
>>> +
>>> + for (error = report->errors; error; error = error->next)
>>> + clar_print_error(i++, _clar.last_report, error);
>>> +}
>>> +
>>> +static void
>>> +clar_report_all(void)
>>> +{
>>> + struct clar_report *report;
>>> + struct clar_error *error;
>>> + int i = 1;
>>> +
>>> + for (report = _clar.reports; report; report = report->next) {
>>> + if (report->status != CL_TEST_FAILURE)
>>> + continue;
>>> +
>>> + for (error = report->errors; error; error = error->next)
>>> + clar_print_error(i++, report, error);
>>> + }
>>> +}
>>> +
>>> +#ifdef WIN32
>>> +# define clar_time DWORD
>>> +
>>> +static void clar_time_now(clar_time *out)
>>> +{
>>> + *out = GetTickCount();
>>> +}
>>> +
>>> +static double clar_time_diff(clar_time *start, clar_time *end)
>>> +{
>>> + return ((double)*end - (double)*start) / 1000;
>>> +}
>>> +#else
>>> +# include <sys/time.h>
>>> +
>>> +# define clar_time struct timeval
>>> +
>>> +static void clar_time_now(clar_time *out)
>>> +{
>>> + struct timezone tz;
>>> +
>>> + gettimeofday(out, &tz);
>>> +}
>>> +
>>> +static double clar_time_diff(clar_time *start, clar_time *end)
>>> +{
>>> + return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
>>> + ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
>>> +}
>>> +#endif
>>> +
>>> +static void
>>> +clar_run_test(
>>> + const struct clar_suite *suite,
>>> + const struct clar_func *test,
>>> + const struct clar_func *initialize,
>>> + const struct clar_func *cleanup)
>>> +{
>>> + clar_time start, end;
>>> +
>>> + _clar.trampoline_enabled = 1;
>>> +
>>> + CL_TRACE(CL_TRACE__TEST__BEGIN);
>>> +
>>> + _clar.last_report->start = time(NULL);
>>> + clar_time_now(&start);
>>> +
>>> + if (setjmp(_clar.trampoline) == 0) {
>>> + if (initialize->ptr != NULL)
>>> + initialize->ptr();
>>> +
>>> + CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
>>> + test->ptr();
>>> + CL_TRACE(CL_TRACE__TEST__RUN_END);
>>> + }
>>> +
>>> + clar_time_now(&end);
>>> +
>>> + _clar.trampoline_enabled = 0;
>>> +
>>> + if (_clar.last_report->status == CL_TEST_NOTRUN)
>>> + _clar.last_report->status = CL_TEST_OK;
>>> +
>>> + _clar.last_report->elapsed = clar_time_diff(&start, &end);
>>> +
>>> + if (_clar.local_cleanup != NULL)
>>> + _clar.local_cleanup(_clar.local_cleanup_payload);
>>> +
>>> + if (cleanup->ptr != NULL)
>>> + cleanup->ptr();
>>> +
>>> + CL_TRACE(CL_TRACE__TEST__END);
>>> +
>>> + _clar.tests_ran++;
>>> +
>>> + /* remove any local-set cleanup methods */
>>> + _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);
>>> + }
>>> +}
>>> +
>>> +static void
>>> +clar_run_suite(const struct clar_suite *suite, const char *filter)
>>> +{
>>> + const struct clar_func *test = suite->tests;
>>> + size_t i, matchlen;
>>> + struct clar_report *report;
>>> + int exact = 0;
>>> +
>>> + if (!suite->enabled)
>>> + return;
>>> +
>>> + if (_clar.exit_on_error && _clar.total_errors)
>>> + return;
>>> +
>>> + if (!_clar.report_errors_only)
>>> + clar_print_onsuite(suite->name, ++_clar.suites_ran);
>>> +
>>> + _clar.active_suite = suite->name;
>>> + _clar.active_test = NULL;
>>> + CL_TRACE(CL_TRACE__SUITE_BEGIN);
>>> +
>>> + if (filter) {
>>> + size_t suitelen = strlen(suite->name);
>>> + matchlen = strlen(filter);
>>> + if (matchlen <= suitelen) {
>>> + filter = NULL;
>>> + } else {
>>> + filter += suitelen;
>>> + while (*filter == ':')
>>> + ++filter;
>>> + matchlen = strlen(filter);
>>> +
>>> + if (matchlen && filter[matchlen - 1] == '$') {
>>> + exact = 1;
>>> + matchlen--;
>>> + }
>>> + }
>>> + }
>>> +
>>> + for (i = 0; i < suite->test_count; ++i) {
>>> + if (filter && strncmp(test[i].name, filter, matchlen))
>>> + continue;
>>> +
>>> + if (exact && strlen(test[i].name) != matchlen)
>>> + continue;
>>> +
>>> + _clar.active_test = test[i].name;
>>> +
>>> + report = calloc(1, sizeof(struct clar_report));
>>> + report->suite = _clar.active_suite;
>>> + report->test = _clar.active_test;
>>> + report->test_number = _clar.tests_ran;
>>> + report->status = CL_TEST_NOTRUN;
>>> +
>>> + if (_clar.reports == NULL)
>>> + _clar.reports = report;
>>> +
>>> + if (_clar.last_report != NULL)
>>> + _clar.last_report->next = report;
>>> +
>>> + _clar.last_report = report;
>>> +
>>> + clar_run_test(suite, &test[i], &suite->initialize,
>>> &suite->cleanup);
>>> +
>>> + if (_clar.exit_on_error && _clar.total_errors)
>>> + return;
>>> + }
>>> +
>>> + _clar.active_test = NULL;
>>> + CL_TRACE(CL_TRACE__SUITE_END);
>>> +}
>>> +
>>> +static void
>>> +clar_usage(const char *arg)
>>> +{
>>> + printf("Usage: %s [options]\n\n", arg);
>>> + printf("Options:\n");
>>> + printf(" -sname Run only the suite with `name` (can go to
>>> individual test name)\n");
>>> + 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 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);
>>> +}
>>> +
>>> +static void
>>> +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) {
>>> + clar_usage(argv[0]);
>>> + }
>>> + }
>>> +
>>> + for (i = 1; i < argc; ++i) {
>>> + char *argument = argv[i];
>>> +
>>> + switch (argument[1]) {
>>> + case 's':
>>> + case 'i':
>>> + case 'x': { /* given suite name */
>>> + int offset = (argument[2] == '=') ? 3 : 2, found = 0;
>>> + char action = argument[1];
>>> + size_t j, arglen, suitelen, cmplen;
>>> +
>>> + argument += offset;
>>> + arglen = strlen(argument);
>>> +
>>> + if (arglen == 0)
>>> + clar_usage(argv[0]);
>>> +
>>> + for (j = 0; j < _clar_suite_count; ++j) {
>>> + suitelen = strlen(_clar_suites[j].name);
>>> + cmplen = (arglen < suitelen) ? arglen :
>>> suitelen;
>>> +
>>> + if (strncmp(argument, _clar_suites[j].name,
>>> cmplen) == 0) {
>>> + int exact = (arglen >= suitelen);
>>> +
>>> + /* Do we have a real suite prefix
>>> separated by a
>>> + * trailing '::' or just a matching
>>> substring? */
>>> + if (arglen > suitelen &&
>>> (argument[suitelen] != ':'
>>> + || argument[suitelen + 1]
>>> != ':'))
>>> + continue;
>>> +
>>> + ++found;
>>> +
>>> + if (!exact)
>>> + _clar.verbosity =
>>> MAX(_clar.verbosity, 1);
>>> +
>>> + switch (action) {
>>> + case 's': {
>>> + struct clar_explicit *explicit
>>> =
>>> + calloc(1,
>>> sizeof(struct clar_explicit));
>>> + assert(explicit);
>>> +
>>> + explicit->suite_idx = j;
>>> + explicit->filter = argument;
>>> +
>>> + if (_clar.explicit == NULL)
>>> + _clar.explicit =
>>> explicit;
>>> +
>>> + if (_clar.last_explicit !=
>>> NULL)
>>> +
>>> _clar.last_explicit->next = explicit;
>>> +
>>> + _clar_suites[j].enabled = 1;
>>> + _clar.last_explicit =
>>> explicit;
>>> + break;
>>> + }
>>> + case 'i': _clar_suites[j].enabled = 1;
>>> break;
>>> + case 'x': _clar_suites[j].enabled = 0;
>>> break;
>>> + }
>>> +
>>> + if (exact)
>>> + break;
>>> + }
>>> + }
>>> +
>>> + if (!found) {
>>> + clar_print_onabort("No suite matching '%s'
>>> found.\n", argument);
>>> + exit(-1);
>>> + }
>>> + break;
>>> + }
>>> +
>>> + case 'q':
>>> + _clar.report_errors_only = 1;
>>> + break;
>>> +
>>> + case 'Q':
>>> + _clar.exit_on_error = 1;
>>> + break;
>>> +
>>> + case 't':
>>> + _clar.output_format = CL_OUTPUT_TAP;
>>> + break;
>>> +
>>> + case 'l': {
>>> + size_t j;
>>> + 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);
>>> +
>>> + exit(0);
>>> + }
>>> +
>>> + case 'v':
>>> + _clar.verbosity++;
>>> + break;
>>> +
>>> + case 'r':
>>> + _clar.write_summary = 1;
>>> + free(_clar.summary_filename);
>>> + _clar.summary_filename = *(argument + 2) ?
>>> strdup(argument + 2) : NULL;
>>> + break;
>>> +
>>> + default:
>>> + assert(!"Unexpected commandline argument!");
>>> + }
>>> + }
>>> +}
>>> +
>>> +void
>>> +clar_test_init(int argc, char **argv)
>>> +{
>>> + const char *summary_env;
>>> +
>>> + if (argc > 1)
>>> + clar_parse_args(argc, argv);
>>> +
>>> + clar_print_init(
>>> + (int)_clar_callback_count,
>>> + (int)_clar_suite_count,
>>> + ""
>>> + );
>>> +
>>> + if (!_clar.summary_filename &&
>>> + (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
>>> + _clar.write_summary = 1;
>>> + _clar.summary_filename = strdup(summary_env);
>>> + }
>>> +
>>> + if (_clar.write_summary && !_clar.summary_filename)
>>> + _clar.summary_filename = strdup("summary.xml");
>>> +
>>> + if (_clar.write_summary &&
>>> + !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
>>> + clar_print_onabort("Failed to open the summary file\n");
>>> + exit(-1);
>>> + }
>>> +
>>> + if (clar_sandbox() < 0) {
>>> + clar_print_onabort("Failed to sandbox the test runner.\n");
>>> + exit(-1);
>>> + }
>>> +}
>>> +
>>> +int
>>> +clar_test_run(void)
>>> +{
>>> + size_t i;
>>> + struct clar_explicit *explicit;
>>> +
>>> + if (_clar.explicit) {
>>> + for (explicit = _clar.explicit; explicit; explicit =
>>> explicit->next)
>>> + clar_run_suite(&_clar_suites[explicit->suite_idx],
>>> explicit->filter);
>>> + } else {
>>> + for (i = 0; i < _clar_suite_count; ++i)
>>> + clar_run_suite(&_clar_suites[i], NULL);
>>> + }
>>> +
>>> + return _clar.total_errors;
>>> +}
>>> +
>>> +void
>>> +clar_test_shutdown(void)
>>> +{
>>> + struct clar_explicit *explicit, *explicit_next;
>>> + struct clar_report *report, *report_next;
>>> +
>>> + clar_print_shutdown(
>>> + _clar.tests_ran,
>>> + (int)_clar_suite_count,
>>> + _clar.total_errors
>>> + );
>>> +
>>> + clar_unsandbox();
>>> +
>>> + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
>>> + clar_print_onabort("Failed to write the summary file\n");
>>> + exit(-1);
>>> + }
>>> +
>>> + for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
>>> + explicit_next = explicit->next;
>>> + free(explicit);
>>> + }
>>> +
>>> + for (report = _clar.reports; report; report = report_next) {
>>> + report_next = report->next;
>>> + free(report);
>>> + }
>>> +
>>> + free(_clar.summary_filename);
>>> +}
>>> +
>>> +int
>>> +clar_test(int argc, char **argv)
>>> +{
>>> + int errors;
>>> +
>>> + clar_test_init(argc, argv);
>>> + errors = clar_test_run();
>>> + clar_test_shutdown();
>>> +
>>> + return errors;
>>> +}
>>> +
>>> +static void abort_test(void)
>>> +{
>>> + if (!_clar.trampoline_enabled) {
>>> + clar_print_onabort(
>>> + "Fatal error: a cleanup method raised an
>>> exception.");
>>> + clar_report_errors(_clar.last_report);
>>> + exit(-1);
>>> + }
>>> +
>>> + CL_TRACE(CL_TRACE__TEST__LONGJMP);
>>> + longjmp(_clar.trampoline, -1);
>>> +}
>>> +
>>> +void clar__skip(void)
>>> +{
>>> + _clar.last_report->status = CL_TEST_SKIP;
>>> + _clar.total_skipped++;
>>> + abort_test();
>>> +}
>>> +
>>> +void clar__fail(
>>> + const char *file,
>>> + const char *function,
>>> + size_t line,
>>> + const char *error_msg,
>>> + const char *description,
>>> + int should_abort)
>>> +{
>>> + struct clar_error *error = calloc(1, sizeof(struct clar_error));
>>> +
>>> + if (_clar.last_report->errors == NULL)
>>> + _clar.last_report->errors = error;
>>> +
>>> + if (_clar.last_report->last_error != NULL)
>>> + _clar.last_report->last_error->next = error;
>>> +
>>> + _clar.last_report->last_error = error;
>>> +
>>> + error->file = file;
>>> + error->function = function;
>>> + error->line_number = line;
>>> + error->error_msg = error_msg;
>>> +
>>> + if (description != NULL)
>>> + error->description = strdup(description);
>>> +
>>> + _clar.total_errors++;
>>> + _clar.last_report->status = CL_TEST_FAILURE;
>>> +
>>> + if (should_abort)
>>> + abort_test();
>>> +}
>>> +
>>> +void clar__assert(
>>> + int condition,
>>> + const char *file,
>>> + const char *function,
>>> + size_t line,
>>> + const char *error_msg,
>>> + const char *description,
>>> + int should_abort)
>>> +{
>>> + if (condition)
>>> + return;
>>> +
>>> + clar__fail(file, function, line, error_msg, description,
>>> should_abort);
>>> +}
>>> +
>>> +void clar__assert_equal(
>>> + const char *file,
>>> + const char *function,
>>> + size_t line,
>>> + const char *err,
>>> + int should_abort,
>>> + const char *fmt,
>>> + ...)
>>> +{
>>> + va_list args;
>>> + char buf[4096];
>>> + int is_equal = 1;
>>> +
>>> + va_start(args, fmt);
>>> +
>>> + if (!strcmp("%s", fmt)) {
>>> + const char *s1 = va_arg(args, const char *);
>>> + const char *s2 = va_arg(args, const char *);
>>> + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
>>> +
>>> + if (!is_equal) {
>>> + if (s1 && s2) {
>>> + int pos;
>>> + for (pos = 0; s1[pos] == s2[pos] && s1[pos] &&
>>> s2[pos]; ++pos)
>>> + /* find differing byte offset */;
>>> + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at
>>> byte %d)",
>>> + s1, s2, pos);
>>> + } else {
>>> + p_snprintf(buf, sizeof(buf), "'%s' != '%s'",
>>> s1, s2);
>>> + }
>>> + }
>>> + }
>>> + else if(!strcmp("%.*s", fmt)) {
>>> + const char *s1 = va_arg(args, const char *);
>>> + const char *s2 = va_arg(args, const char *);
>>> + int len = va_arg(args, int);
>>> + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
>>> +
>>> + if (!is_equal) {
>>> + if (s1 && s2) {
>>> + int pos;
>>> + for (pos = 0; s1[pos] == s2[pos] && pos < len;
>>> ++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);
>>> + }
>>> + }
>>> + }
>>> + else if (!strcmp("%ls", fmt)) {
>>> + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
>>> + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
>>> + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1,
>>> wcs2);
>>> +
>>> + if (!is_equal) {
>>> + if (wcs1 && wcs2) {
>>> + int pos;
>>> + for (pos = 0; wcs1[pos] == wcs2[pos] &&
>>> wcs1[pos] && wcs2[pos]; ++pos)
>>> + /* find differing byte offset */;
>>> + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'
>>> (at byte %d)",
>>> + wcs1, wcs2, pos);
>>> + } else {
>>> + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'",
>>> wcs1, wcs2);
>>> + }
>>> + }
>>> + }
>>> + else if(!strcmp("%.*ls", fmt)) {
>>> + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
>>> + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
>>> + int len = va_arg(args, int);
>>> + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1,
>>> wcs2, len);
>>> +
>>> + if (!is_equal) {
>>> + if (wcs1 && wcs2) {
>>> + int pos;
>>> + for (pos = 0; wcs1[pos] == wcs2[pos] && pos <
>>> len; ++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);
>>> + }
>>> + }
>>> + }
>>> + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
>>> + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
>>> + is_equal = (sz1 == sz2);
>>> + if (!is_equal) {
>>> + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
>>> + strncat(buf, " != ", sizeof(buf) - offset);
>>> + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4,
>>> fmt, sz2);
>>> + }
>>> + }
>>> + else if (!strcmp("%p", fmt)) {
>>> + 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);
>>> + }
>>> + else {
>>> + int i1 = va_arg(args, int), i2 = va_arg(args, int);
>>> + is_equal = (i1 == i2);
>>> + if (!is_equal) {
>>> + int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
>>> + strncat(buf, " != ", sizeof(buf) - offset);
>>> + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4,
>>> fmt, i2);
>>> + }
>>> + }
>>> +
>>> + va_end(args);
>>> +
>>> + if (!is_equal)
>>> + clar__fail(file, function, line, err, buf, should_abort);
>>> +}
>>> +
>>> +void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
>>> +{
>>> + _clar.local_cleanup = cleanup;
>>> + _clar.local_cleanup_payload = opaque;
>>> +}
>>> +
>>> +#include "clar/sandbox.h"
>>> +#include "clar/fixtures.h"
>>> +#include "clar/fs.h"
>>> +#include "clar/print.h"
>>> +#include "clar/summary.h"
>>> diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
>>> new file mode 100644
>>> index 0000000000..8c22382bd5
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar.h
>>> @@ -0,0 +1,173 @@
>>> +/*
>>> + * 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_H__
>>> +#define __CLAR_TEST_H__
>>> +
>>> +#include <stdlib.h>
>>> +
>>> +enum cl_test_status {
>>> + CL_TEST_OK,
>>> + CL_TEST_FAILURE,
>>> + CL_TEST_SKIP,
>>> + CL_TEST_NOTRUN,
>>> +};
>>> +
>>> +enum cl_output_format {
>>> + CL_OUTPUT_CLAP,
>>> + CL_OUTPUT_TAP,
>>> +};
>>> +
>>> +/** Setup clar environment */
>>> +void clar_test_init(int argc, char *argv[]);
>>> +int clar_test_run(void);
>>> +void clar_test_shutdown(void);
>>> +
>>> +/** One shot setup & run */
>>> +int clar_test(int argc, char *argv[]);
>>> +
>>> +const char *clar_sandbox_path(void);
>>> +
>>> +void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
>>> +void cl_fs_cleanup(void);
>>> +
>>> +/**
>>> + * cl_trace_* is a hook to provide a simple global tracing
>>> + * mechanism.
>>> + *
>>> + * The goal here is to let main() provide clar-proper
>>> + * with a callback to optionally write log info for
>>> + * test operations into the same stream used by their
>>> + * actual tests. This would let them print test names
>>> + * and maybe performance data as they choose.
>>> + *
>>> + * The goal is NOT to alter the flow of control or to
>>> + * override test selection/skipping. (So the callback
>>> + * does not return a value.)
>>> + *
>>> + * The goal is NOT to duplicate the existing
>>> + * pass/fail/skip reporting. (So the callback
>>> + * does not accept a status/errorcode argument.)
>>> + *
>>> + */
>>> +typedef enum cl_trace_event {
>>> + CL_TRACE__SUITE_BEGIN,
>>> + CL_TRACE__SUITE_END,
>>> + CL_TRACE__TEST__BEGIN,
>>> + CL_TRACE__TEST__END,
>>> + CL_TRACE__TEST__RUN_BEGIN,
>>> + CL_TRACE__TEST__RUN_END,
>>> + CL_TRACE__TEST__LONGJMP,
>>> +} cl_trace_event;
>>> +
>>> +typedef void (cl_trace_cb)(
>>> + cl_trace_event ev,
>>> + const char *suite_name,
>>> + const char *test_name,
>>> + void *payload);
>>> +
>>> +/**
>>> + * Register a callback into CLAR to send global trace events.
>>> + * Pass NULL to disable.
>>> + */
>>> +void cl_trace_register(cl_trace_cb *cb, void *payload);
>>> +
>>> +
>>> +#ifdef CLAR_FIXTURE_PATH
>>> +const char *cl_fixture(const char *fixture_name);
>>> +void cl_fixture_sandbox(const char *fixture_name);
>>> +void cl_fixture_cleanup(const char *fixture_name);
>>> +const char *cl_fixture_basename(const char *fixture_name);
>>> +#endif
>>> +
>>> +/**
>>> + * 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)
>>> +
>>> +/**
>>> + * 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)
>>> +
>>> +/**
>>> + * Assertion macros with no error message
>>> + */
>>> +#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
>>> +#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
>>> +#define cl_assert(expr) cl_assert_(expr, NULL)
>>> +
>>> +/**
>>> + * Check macros with no error message
>>> + */
>>> +#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
>>> +#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
>>> +#define cl_check(expr) cl_check_(expr, NULL)
>>> +
>>> +/**
>>> + * 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_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_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_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_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_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_b(b1,b2)
>>> clar__assert_equal(__FILE__,__func__,__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))
>>> +
>>> +void clar__skip(void);
>>> +
>>> +void clar__fail(
>>> + const char *file,
>>> + const char *func,
>>> + size_t line,
>>> + const char *error,
>>> + const char *description,
>>> + int should_abort);
>>> +
>>> +void clar__assert(
>>> + int condition,
>>> + const char *file,
>>> + const char *func,
>>> + size_t line,
>>> + const char *error,
>>> + const char *description,
>>> + int should_abort);
>>> +
>>> +void clar__assert_equal(
>>> + const char *file,
>>> + const char *func,
>>> + size_t line,
>>> + const char *err,
>>> + int should_abort,
>>> + const char *fmt,
>>> + ...);
>>> +
>>> +#endif
>>> diff --git a/t/unit-tests/clar/clar/fixtures.h
>>> b/t/unit-tests/clar/clar/fixtures.h
>>> new file mode 100644
>>> index 0000000000..6ec6423484
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar/fixtures.h
>>> @@ -0,0 +1,50 @@
>>> +#ifdef CLAR_FIXTURE_PATH
>>> +static const char *
>>> +fixture_path(const char *base, const char *fixture_name)
>>> +{
>>> + static char _path[4096];
>>> + size_t root_len;
>>> +
>>> + root_len = strlen(base);
>>> + strncpy(_path, base, sizeof(_path));
>>> +
>>> + if (_path[root_len - 1] != '/')
>>> + _path[root_len++] = '/';
>>> +
>>> + if (fixture_name[0] == '/')
>>> + fixture_name++;
>>> +
>>> + strncpy(_path + root_len,
>>> + fixture_name,
>>> + sizeof(_path) - root_len);
>>> +
>>> + return _path;
>>> +}
>>> +
>>> +const char *cl_fixture(const char *fixture_name)
>>> +{
>>> + return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
>>> +}
>>> +
>>> +void cl_fixture_sandbox(const char *fixture_name)
>>> +{
>>> + fs_copy(cl_fixture(fixture_name), _clar_path);
>>> +}
>>> +
>>> +const char *cl_fixture_basename(const char *fixture_name)
>>> +{
>>> + const char *p;
>>> +
>>> + for (p = fixture_name; *p; p++) {
>>> + if (p[0] == '/' && p[1] && p[1] != '/')
>>> + fixture_name = p+1;
>>> + }
>>> +
>>> + return fixture_name;
>>> +}
>>> +
>>> +void cl_fixture_cleanup(const char *fixture_name)
>>> +{
>>> + fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
>>> +}
>>> +#endif
>>> diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
>>> new file mode 100644
>>> index 0000000000..3e39890bd3
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar/fs.h
>>> @@ -0,0 +1,522 @@
>>> +/*
>>> + * By default, use a read/write loop to copy files on POSIX systems.
>>> + * On Linux, use sendfile by default as it's slightly faster. On
>>> + * macOS, we avoid fcopyfile by default because it's slightly slower.
>>> + */
>>> +#undef USE_FCOPYFILE
>>> +#define USE_SENDFILE 1
>>> +
>>> +#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
>>> +
>>> +#ifdef __MINGW32__
>>> +
>>> +/* These security-enhanced functions are not available
>>> + * in MinGW, so just use the vanilla ones */
>>> +#define wcscpy_s(a, b, c) wcscpy((a), (c))
>>> +#define wcscat_s(a, b, c) wcscat((a), (c))
>>> +
>>> +#endif /* __MINGW32__ */
>>> +
>>> +static int
>>> +fs__dotordotdot(WCHAR *_tocheck)
>>> +{
>>> + return _tocheck[0] == '.' &&
>>> + (_tocheck[1] == '\0' ||
>>> + (_tocheck[1] == '.' && _tocheck[2] == '\0'));
>>> +}
>>> +
>>> +static int
>>> +fs_rmdir_rmdir(WCHAR *_wpath)
>>> +{
>>> + unsigned retries = 1;
>>> +
>>> + while (!RemoveDirectoryW(_wpath)) {
>>> + /* Only retry when we have retries remaining, and the
>>> + * error was ERROR_DIR_NOT_EMPTY. */
>>> + if (retries++ > RM_RETRY_COUNT ||
>>> + ERROR_DIR_NOT_EMPTY != GetLastError())
>>> + return -1;
>>> +
>>> + /* Give whatever has a handle to a child item some time
>>> + * to release it before trying again */
>>> + Sleep(RM_RETRY_DELAY * retries * retries);
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void translate_path(WCHAR *path, size_t path_size)
>>> +{
>>> + size_t path_len, i;
>>> +
>>> + if (wcsncmp(path, L"\\\\?\\", 4) == 0)
>>> + return;
>>> +
>>> + path_len = wcslen(path);
>>> + cl_assert(path_size > path_len + 4);
>>> +
>>> + for (i = path_len; i > 0; i--) {
>>> + WCHAR c = path[i - 1];
>>> +
>>> + if (c == L'/')
>>> + path[i + 3] = L'\\';
>>> + else
>>> + path[i + 3] = path[i - 1];
>>> + }
>>> +
>>> + path[0] = L'\\';
>>> + path[1] = L'\\';
>>> + path[2] = L'?';
>>> + path[3] = L'\\';
>>> + path[path_len + 4] = L'\0';
>>> +}
>>> +
>>> +static void
>>> +fs_rmdir_helper(WCHAR *_wsource)
>>> +{
>>> + WCHAR buffer[CLAR_MAX_PATH];
>>> + HANDLE find_handle;
>>> + WIN32_FIND_DATAW find_data;
>>> + size_t buffer_prefix_len;
>>> +
>>> + /* Set up the buffer and capture the length */
>>> + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
>>> + translate_path(buffer, CLAR_MAX_PATH);
>>> + wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
>>> + buffer_prefix_len = wcslen(buffer);
>>> +
>>> + /* FindFirstFile needs a wildcard to match multiple items */
>>> + wcscat_s(buffer, CLAR_MAX_PATH, L"*");
>>> + find_handle = FindFirstFileW(buffer, &find_data);
>>> + cl_assert(INVALID_HANDLE_VALUE != find_handle);
>>> +
>>> + do {
>>> + /* FindFirstFile/FindNextFile gives back . and ..
>>> + * entries at the beginning */
>>> + if (fs__dotordotdot(find_data.cFileName))
>>> + continue;
>>> +
>>> + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH -
>>> buffer_prefix_len, find_data.cFileName);
>>> +
>>> + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
>>> + fs_rmdir_helper(buffer);
>>> + else {
>>> + /* If set, the +R bit must be cleared before deleting
>>> */
>>> + if (FILE_ATTRIBUTE_READONLY &
>>> find_data.dwFileAttributes)
>>> + cl_assert(SetFileAttributesW(buffer,
>>> find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
>>> +
>>> + cl_assert(DeleteFileW(buffer));
>>> + }
>>> + }
>>> + while (FindNextFileW(find_handle, &find_data));
>>> +
>>> + /* Ensure that we successfully completed the enumeration */
>>> + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
>>> +
>>> + /* Close the find handle */
>>> + FindClose(find_handle);
>>> +
>>> + /* Now that the directory is empty, remove it */
>>> + cl_assert(0 == fs_rmdir_rmdir(_wsource));
>>> +}
>>> +
>>> +static int
>>> +fs_rm_wait(WCHAR *_wpath)
>>> +{
>>> + unsigned retries = 1;
>>> + DWORD last_error;
>>> +
>>> + do {
>>> + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
>>> + last_error = GetLastError();
>>> + else
>>> + last_error = ERROR_SUCCESS;
>>> +
>>> + /* Is the item gone? */
>>> + if (ERROR_FILE_NOT_FOUND == last_error ||
>>> + ERROR_PATH_NOT_FOUND == last_error)
>>> + return 0;
>>> +
>>> + Sleep(RM_RETRY_DELAY * retries * retries);
>>> + }
>>> + while (retries++ <= RM_RETRY_COUNT);
>>> +
>>> + return -1;
>>> +}
>>> +
>>> +static void
>>> +fs_rm(const char *_source)
>>> +{
>>> + WCHAR wsource[CLAR_MAX_PATH];
>>> + DWORD attrs;
>>> +
>>> + /* The input path is UTF-8. Convert it to wide characters
>>> + * for use with the Windows API */
>>> + cl_assert(MultiByteToWideChar(CP_UTF8,
>>> + MB_ERR_INVALID_CHARS,
>>> + _source,
>>> + -1, /* Indicates NULL termination */
>>> + wsource,
>>> + CLAR_MAX_PATH));
>>> +
>>> + translate_path(wsource, CLAR_MAX_PATH);
>>> +
>>> + /* Does the item exist? If not, we have no work to do */
>>> + attrs = GetFileAttributesW(wsource);
>>> +
>>> + if (INVALID_FILE_ATTRIBUTES == attrs)
>>> + return;
>>> +
>>> + if (FILE_ATTRIBUTE_DIRECTORY & attrs)
>>> + fs_rmdir_helper(wsource);
>>> + else {
>>> + /* The item is a file. Strip the +R bit */
>>> + if (FILE_ATTRIBUTE_READONLY & attrs)
>>> + cl_assert(SetFileAttributesW(wsource, attrs &
>>> ~FILE_ATTRIBUTE_READONLY));
>>> +
>>> + cl_assert(DeleteFileW(wsource));
>>> + }
>>> +
>>> + /* Wait for the DeleteFile or RemoveDirectory call to complete */
>>> + cl_assert(0 == fs_rm_wait(wsource));
>>> +}
>>> +
>>> +static void
>>> +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
>>> +{
>>> + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
>>> + HANDLE find_handle;
>>> + WIN32_FIND_DATAW find_data;
>>> + size_t buf_source_prefix_len, buf_dest_prefix_len;
>>> +
>>> + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
>>> + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
>>> + translate_path(buf_source, CLAR_MAX_PATH);
>>> + buf_source_prefix_len = wcslen(buf_source);
>>> +
>>> + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
>>> + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
>>> + translate_path(buf_dest, CLAR_MAX_PATH);
>>> + buf_dest_prefix_len = wcslen(buf_dest);
>>> +
>>> + /* Get an enumerator for the items in the source. */
>>> + wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
>>> + find_handle = FindFirstFileW(buf_source, &find_data);
>>> + cl_assert(INVALID_HANDLE_VALUE != find_handle);
>>> +
>>> + /* Create the target directory. */
>>> + cl_assert(CreateDirectoryW(_wdest, NULL));
>>> +
>>> + do {
>>> + /* FindFirstFile/FindNextFile gives back . and ..
>>> + * entries at the beginning */
>>> + if (fs__dotordotdot(find_data.cFileName))
>>> + continue;
>>> +
>>> + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH -
>>> buf_source_prefix_len, find_data.cFileName);
>>> + wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH -
>>> buf_dest_prefix_len, find_data.cFileName);
>>> +
>>> + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
>>> + fs_copydir_helper(buf_source, buf_dest);
>>> + else
>>> + cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
>>> + }
>>> + while (FindNextFileW(find_handle, &find_data));
>>> +
>>> + /* Ensure that we successfully completed the enumeration */
>>> + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
>>> +
>>> + /* Close the find handle */
>>> + FindClose(find_handle);
>>> +}
>>> +
>>> +static void
>>> +fs_copy(const char *_source, const char *_dest)
>>> +{
>>> + WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
>>> + DWORD source_attrs, dest_attrs;
>>> + HANDLE find_handle;
>>> + WIN32_FIND_DATAW find_data;
>>> +
>>> + /* The input paths are UTF-8. Convert them to wide characters
>>> + * for use with the Windows API. */
>>> + cl_assert(MultiByteToWideChar(CP_UTF8,
>>> + MB_ERR_INVALID_CHARS,
>>> + _source,
>>> + -1,
>>> + wsource,
>>> + CLAR_MAX_PATH));
>>> +
>>> + cl_assert(MultiByteToWideChar(CP_UTF8,
>>> + MB_ERR_INVALID_CHARS,
>>> + _dest,
>>> + -1,
>>> + wdest,
>>> + CLAR_MAX_PATH));
>>> +
>>> + translate_path(wsource, CLAR_MAX_PATH);
>>> + translate_path(wdest, CLAR_MAX_PATH);
>>> +
>>> + /* Check the source for existence */
>>> + source_attrs = GetFileAttributesW(wsource);
>>> + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
>>> +
>>> + /* Check the target for existence */
>>> + dest_attrs = GetFileAttributesW(wdest);
>>> +
>>> + if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
>>> + /* Target exists; append last path part of source to target.
>>> + * Use FindFirstFile to parse the path */
>>> + find_handle = FindFirstFileW(wsource, &find_data);
>>> + cl_assert(INVALID_HANDLE_VALUE != find_handle);
>>> + wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
>>> + wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
>>> + FindClose(find_handle);
>>> +
>>> + /* Check the new target for existence */
>>> + cl_assert(INVALID_FILE_ATTRIBUTES ==
>>> GetFileAttributesW(wdest));
>>> + }
>>> +
>>> + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
>>> + fs_copydir_helper(wsource, wdest);
>>> + else
>>> + cl_assert(CopyFileW(wsource, wdest, TRUE));
>>> +}
>>> +
>>> +void
>>> +cl_fs_cleanup(void)
>>> +{
>>> +#ifdef CLAR_FIXTURE_PATH
>>> + fs_rm(fixture_path(_clar_path, "*"));
>>> +#endif
>>> +}
>>> +
>>> +#else
>>> +
>>> +#include <errno.h>
>>> +#include <string.h>
>>> +#include <limits.h>
>>> +#include <dirent.h>
>>> +#include <fcntl.h>
>>> +#include <unistd.h>
>>> +#include <sys/types.h>
>>> +#include <sys/stat.h>
>>> +
>>> +#if defined(__linux__)
>>> +# include <sys/sendfile.h>
>>> +#endif
>>> +
>>> +#if defined(__APPLE__)
>>> +# include <copyfile.h>
>>> +#endif
>>> +
>>> +static void basename_r(const char **out, int *out_len, const char *in)
>>> +{
>>> + size_t in_len = strlen(in), start_pos;
>>> +
>>> + for (in_len = strlen(in); in_len; in_len--) {
>>> + if (in[in_len - 1] != '/')
>>> + break;
>>> + }
>>> +
>>> + for (start_pos = in_len; start_pos; start_pos--) {
>>> + if (in[start_pos - 1] == '/')
>>> + break;
>>> + }
>>> +
>>> + cl_assert(in_len - start_pos < INT_MAX);
>>> +
>>> + if (in_len - start_pos > 0) {
>>> + *out = &in[start_pos];
>>> + *out_len = (in_len - start_pos);
>>> + } else {
>>> + *out = "/";
>>> + *out_len = 1;
>>> + }
>>> +}
>>> +
>>> +static char *joinpath(const char *dir, const char *base, int base_len)
>>> +{
>>> + char *out;
>>> + int len;
>>> +
>>> + if (base_len == -1) {
>>> + size_t bl = strlen(base);
>>> +
>>> + cl_assert(bl < INT_MAX);
>>> + base_len = (int)bl;
>>> + }
>>> +
>>> + len = strlen(dir) + base_len + 2;
>>> + cl_assert(len > 0);
>>> +
>>> + cl_assert(out = malloc(len));
>>> + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
>>> +
>>> + return out;
>>> +}
>>> +
>>> +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");
>>> + while ((d = (errno = 0, readdir(source_dir))) != NULL) {
>>> + char *child;
>>> +
>>> + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
>>> + continue;
>>> +
>>> + child = joinpath(source, d->d_name, -1);
>>> + fs_copy(child, dest);
>>> + free(child);
>>> + }
>>> +
>>> + cl_assert_(errno == 0, "Failed to iterate source dir");
>>> +
>>> + closedir(source_dir);
>>> +}
>>> +
>>> +static void
>>> +fs_copyfile_helper(const char *source, size_t source_len, const char *dest,
>>> int dest_mode)
>>> +{
>>> + int in, out;
>>> +
>>> + cl_must_pass((in = open(source, O_RDONLY)));
>>> + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
>>> +
>>> +#if USE_FCOPYFILE && defined(__APPLE__)
>>> + ((void)(source_len)); /* unused */
>>> + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
>>> +#elif USE_SENDFILE && defined(__linux__)
>>> + {
>>> + ssize_t ret = 0;
>>> +
>>> + while (source_len && (ret = sendfile(out, in, NULL,
>>> source_len)) > 0) {
>>> + source_len -= (size_t)ret;
>>> + }
>>> + cl_assert(ret >= 0);
>>> + }
>>> +#else
>>> + {
>>> + char buf[131072];
>>> + ssize_t ret;
>>> +
>>> + ((void)(source_len)); /* unused */
>>> +
>>> + while ((ret = read(in, buf, sizeof(buf))) > 0) {
>>> + size_t len = (size_t)ret;
>>> +
>>> + while (len && (ret = write(out, buf, len)) > 0) {
>>> + cl_assert(ret <= (ssize_t)len);
>>> + len -= ret;
>>> + }
>>> + cl_assert(ret >= 0);
>>> + }
>>> + cl_assert(ret == 0);
>>> + }
>>> +#endif
>>> +
>>> + close(in);
>>> + close(out);
>>> +}
>>> +
>>> +static void
>>> +fs_copy(const char *source, const char *_dest)
>>> +{
>>> + char *dbuf = NULL;
>>> + const char *dest = NULL;
>>> + struct stat source_st, dest_st;
>>> +
>>> + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy
>>> source");
>>> +
>>> + if (lstat(_dest, &dest_st) == 0) {
>>> + const char *base;
>>> + int base_len;
>>> +
>>> + /* Target exists and is directory; append basename */
>>> + cl_assert(S_ISDIR(dest_st.st_mode));
>>> +
>>> + basename_r(&base, &base_len, source);
>>> + cl_assert(base_len < INT_MAX);
>>> +
>>> + dbuf = joinpath(_dest, base, base_len);
>>> + dest = dbuf;
>>> + } else if (errno != ENOENT) {
>>> + cl_fail("Cannot copy; cannot stat destination");
>>> + } else {
>>> + dest = _dest;
>>> + }
>>> +
>>> + if (S_ISDIR(source_st.st_mode)) {
>>> + fs_copydir_helper(source, dest, source_st.st_mode);
>>> + } else {
>>> + fs_copyfile_helper(source, source_st.st_size, dest,
>>> source_st.st_mode);
>>> + }
>>> +
>>> + free(dbuf);
>>> +}
>>> +
>>> +static void
>>> +fs_rmdir_helper(const char *path)
>>> +{
>>> + DIR *dir;
>>> + struct dirent *d;
>>> +
>>> + cl_assert_(dir = opendir(path), "Could not open dir");
>>> + while ((d = (errno = 0, readdir(dir))) != NULL) {
>>> + char *child;
>>> +
>>> + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
>>> + continue;
>>> +
>>> + child = joinpath(path, d->d_name, -1);
>>> + fs_rm(child);
>>> + free(child);
>>> + }
>>> +
>>> + cl_assert_(errno == 0, "Failed to iterate source dir");
>>> + closedir(dir);
>>> +
>>> + cl_must_pass_(rmdir(path), "Could not remove directory");
>>> +}
>>> +
>>> +static void
>>> +fs_rm(const char *path)
>>> +{
>>> + struct stat st;
>>> +
>>> + if (lstat(path, &st)) {
>>> + if (errno == ENOENT)
>>> + return;
>>> +
>>> + cl_fail("Cannot copy; cannot stat destination");
>>> + }
>>> +
>>> + if (S_ISDIR(st.st_mode)) {
>>> + fs_rmdir_helper(path);
>>> + } else {
>>> + cl_must_pass(unlink(path));
>>> + }
>>> +}
>>> +
>>> +void
>>> +cl_fs_cleanup(void)
>>> +{
>>> + clar_unsandbox();
>>> + clar_sandbox();
>>> +}
>>> +#endif
>>> diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
>>> new file mode 100644
>>> index 0000000000..c17e2f693b
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar/print.h
>>> @@ -0,0 +1,211 @@
>>> +/* clap: clar protocol, the traditional clar output format */
>>> +
>>> +static void clar_print_clap_init(int test_count, int suite_count, const
>>> char *suite_names)
>>> +{
>>> + (void)test_count;
>>> + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
>>> + printf("Started (test status codes: OK='.' FAILURE='F'
>>> SKIPPED='S')\n");
>>> +}
>>> +
>>> +static void clar_print_clap_shutdown(int test_count, int suite_count, int
>>> error_count)
>>> +{
>>> + (void)test_count;
>>> + (void)suite_count;
>>> + (void)error_count;
>>> +
>>> + printf("\n\n");
>>> + clar_report_all();
>>> +}
>>> +
>>> +static void clar_print_clap_error(int num, const struct clar_report
>>> *report, const struct clar_error *error)
>>> +{
>>> + printf(" %d) Failure:\n", num);
>>> +
>>> + printf("%s::%s [%s:%"PRIuZ"]\n",
>>> + report->suite,
>>> + report->test,
>>> + error->file,
>>> + error->line_number);
>>> +
>>> + printf(" %s\n", error->error_msg);
>>> +
>>> + if (error->description != NULL)
>>> + printf(" %s\n", error->description);
>>> +
>>> + printf("\n");
>>> + fflush(stdout);
>>> +}
>>> +
>>> +static void clar_print_clap_ontest(const char *suite_name, const char
>>> *test_name, int test_number, enum cl_test_status status)
>>> +{
>>> + (void)test_name;
>>> + (void)test_number;
>>> +
>>> + 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;
>>> + }
>>> + } else {
>>> + switch (status) {
>>> + case CL_TEST_OK: printf("."); break;
>>> + case CL_TEST_FAILURE: printf("F"); break;
>>> + case CL_TEST_SKIP: printf("S"); break;
>>> + case CL_TEST_NOTRUN: printf("N"); break;
>>> + }
>>> +
>>> + fflush(stdout);
>>> + }
>>> +}
>>> +
>>> +static void clar_print_clap_onsuite(const char *suite_name, int
>>> suite_index)
>>> +{
>>> + if (_clar.verbosity == 1)
>>> + printf("\n%s", suite_name);
>>> +
>>> + (void)suite_index;
>>> +}
>>> +
>>> +static void clar_print_clap_onabort(const char *fmt, va_list arg)
>>> +{
>>> + vfprintf(stderr, fmt, arg);
>>> +}
>>> +
>>> +/* tap: test anywhere protocol format */
>>> +
>>> +static void clar_print_tap_init(int test_count, int suite_count, const char
>>> *suite_names)
>>> +{
>>> + (void)test_count;
>>> + (void)suite_count;
>>> + (void)suite_names;
>>> + printf("TAP version 13\n");
>>> +}
>>> +
>>> +static void clar_print_tap_shutdown(int test_count, int suite_count, int
>>> error_count)
>>> +{
>>> + (void)suite_count;
>>> + (void)error_count;
>>> +
>>> + printf("1..%d\n", test_count);
>>> +}
>>> +
>>> +static void clar_print_tap_error(int num, const struct clar_report *report,
>>> const struct clar_error *error)
>>> +{
>>> + (void)num;
>>> + (void)report;
>>> + (void)error;
>>> +}
>>> +
>>> +static void print_escaped(const char *str)
>>> +{
>>> + char *c;
>>> +
>>> + while ((c = strchr(str, '\'')) != NULL) {
>>> + printf("%.*s", (int)(c - str), str);
>>> + printf("''");
>>> + str = c + 1;
>>> + }
>>> +
>>> + printf("%s", str);
>>> +}
>>> +
>>> +static void clar_print_tap_ontest(const char *suite_name, const char
>>> *test_name, int test_number, enum cl_test_status status)
>>> +{
>>> + const struct clar_error *error = _clar.last_report->errors;
>>> +
>>> + (void)test_name;
>>> + (void)test_number;
>>> +
>>> + switch(status) {
>>> + case CL_TEST_OK:
>>> + printf("ok %d - %s::%s\n", test_number, suite_name,
>>> test_name);
>>> + break;
>>> + 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 (error->description)
>>> + printf(" %s\n", error->description);
>>> +
>>> + printf(" at:\n");
>>> + printf(" file: '"); print_escaped(error->file);
>>> printf("'\n");
>>> + printf(" line: %" PRIuZ "\n", error->line_number);
>>> + printf(" function: '%s'\n", error->function);
>>> + printf(" ---\n");
>>> +
>>> + break;
>>> + case CL_TEST_SKIP:
>>> + case CL_TEST_NOTRUN:
>>> + printf("ok %d - # SKIP %s::%s\n", test_number, suite_name,
>>> test_name);
>>> + break;
>>> + }
>>> +
>>> + fflush(stdout);
>>> +}
>>> +
>>> +static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
>>> +{
>>> + printf("# start of suite %d: %s\n", suite_index, suite_name);
>>> +}
>>> +
>>> +static void clar_print_tap_onabort(const char *fmt, va_list arg)
>>> +{
>>> + printf("Bail out! ");
>>> + vprintf(fmt, arg);
>>> + fflush(stdout);
>>> +}
>>> +
>>> +/* indirection between protocol output selection */
>>> +
>>> +#define PRINT(FN, ...) do { \
>>> + switch (_clar.output_format) { \
>>> + case CL_OUTPUT_CLAP: \
>>> + clar_print_clap_##FN (__VA_ARGS__); \
>>> + break; \
>>> + case CL_OUTPUT_TAP: \
>>> + clar_print_tap_##FN (__VA_ARGS__); \
>>> + break; \
>>> + default: \
>>> + abort(); \
>>> + } \
>>> + } while (0)
>>> +
>>> +static void clar_print_init(int test_count, int suite_count, const char
>>> *suite_names)
>>> +{
>>> + PRINT(init, test_count, suite_count, suite_names);
>>> +}
>>> +
>>> +static void clar_print_shutdown(int test_count, int suite_count, int
>>> error_count)
>>> +{
>>> + PRINT(shutdown, test_count, suite_count, error_count);
>>> +}
>>> +
>>> +static void clar_print_error(int num, const struct clar_report *report,
>>> const struct clar_error *error)
>>> +{
>>> + PRINT(error, num, report, error);
>>> +}
>>> +
>>> +static void clar_print_ontest(const char *suite_name, const char
>>> *test_name, int test_number, enum cl_test_status status)
>>> +{
>>> + PRINT(ontest, suite_name, test_name, test_number, status);
>>> +}
>>> +
>>> +static void clar_print_onsuite(const char *suite_name, int suite_index)
>>> +{
>>> + PRINT(onsuite, suite_name, suite_index);
>>> +}
>>> +
>>> +static void clar_print_onabort(const char *msg, ...)
>>> +{
>>> + va_list argp;
>>> + va_start(argp, msg);
>>> + PRINT(onabort, msg, argp);
>>> + va_end(argp);
>>> +}
>>> diff --git a/t/unit-tests/clar/clar/sandbox.h
>>> b/t/unit-tests/clar/clar/sandbox.h
>>> new file mode 100644
>>> index 0000000000..7c177f3525
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar/sandbox.h
>>> @@ -0,0 +1,153 @@
>>> +#ifdef __APPLE__
>>> +#include <sys/syslimits.h>
>>> +#endif
>>> +
>>> +static char _clar_path[4096 + 1];
>>> +
>>> +static int
>>> +is_valid_tmp_path(const char *path)
>>> +{
>>> + STAT_T st;
>>> +
>>> + if (stat(path, &st) != 0)
>>> + return 0;
>>> +
>>> + if (!S_ISDIR(st.st_mode))
>>> + return 0;
>>> +
>>> + return (access(path, W_OK) == 0);
>>> +}
>>> +
>>> +static int
>>> +find_tmp_path(char *buffer, size_t length)
>>> +{
>>> +#ifndef _WIN32
>>> + static const size_t var_count = 5;
>>> + static const char *env_vars[] = {
>>> + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
>>> + };
>>> +
>>> + size_t i;
>>> +
>>> + 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;
>>> + }
>>> + }
>>> +
>>> + /* 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)
>>> + return 0;
>>> +
>>> + if (GetTempPath((DWORD)length, buffer))
>>> + return 0;
>>> +#endif
>>> +
>>> + /* This system doesn't like us, try to use the current directory */
>>> + if (is_valid_tmp_path(".")) {
>>> + strncpy(buffer, ".", length - 1);
>>> + buffer[length - 1] = '\0';
>>> + return 0;
>>> + }
>>> +
>>> + return -1;
>>> +}
>>> +
>>> +static void clar_unsandbox(void)
>>> +{
>>> + if (_clar_path[0] == '\0')
>>> + return;
>>> +
>>> + cl_must_pass(chdir(".."));
>>> +
>>> + fs_rm(_clar_path);
>>> +}
>>> +
>>> +static int build_sandbox_path(void)
>>> +{
>>> +#ifdef CLAR_TMPDIR
>>> + const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
>>> +#else
>>> + const char path_tail[] = "clar_tmp_XXXXXX";
>>> +#endif
>>> +
>>> + size_t len;
>>> +
>>> + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
>>> + return -1;
>>> +
>>> + len = strlen(_clar_path);
>>> +
>>> +#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 (_clar_path[len - 1] != '/') {
>>> + _clar_path[len++] = '/';
>>> + }
>>> +
>>> + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
>>> +
>>> +#if defined(__MINGW32__)
>>> + if (_mktemp(_clar_path) == NULL)
>>> + return -1;
>>> +
>>> + if (mkdir(_clar_path, 0700) != 0)
>>> + return -1;
>>> +#elif defined(_WIN32)
>>> + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
>>> + return -1;
>>> +
>>> + if (mkdir(_clar_path, 0700) != 0)
>>> + return -1;
>>> +#else
>>> + if (mkdtemp(_clar_path) == NULL)
>>> + return -1;
>>> +#endif
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int clar_sandbox(void)
>>> +{
>>> + if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
>>> + return -1;
>>> +
>>> + if (chdir(_clar_path) != 0)
>>> + return -1;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +const char *clar_sandbox_path(void)
>>> +{
>>> + return _clar_path;
>>> +}
>>> diff --git a/t/unit-tests/clar/clar/summary.h
>>> b/t/unit-tests/clar/clar/summary.h
>>> new file mode 100644
>>> index 0000000000..4dd352e28b
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar/summary.h
>>> @@ -0,0 +1,143 @@
>>> +
>>> +#include <stdio.h>
>>> +#include <time.h>
>>> +
>>> +static int clar_summary_close_tag(
>>> + struct clar_summary *summary, const char *tag, int indent)
>>> +{
>>> + const char *indt;
>>> +
>>> + if (indent == 0) indt = "";
>>> + else if (indent == 1) indt = "\t";
>>> + else indt = "\t\t";
>>> +
>>> + return fprintf(summary->fp, "%s</%s>\n", indt, tag);
>>> +}
>>> +
>>> +static int clar_summary_testsuites(struct clar_summary *summary)
>>> +{
>>> + return fprintf(summary->fp, "<testsuites>\n");
>>> +}
>>> +
>>> +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(×tamp);
>>> + char iso_dt[20];
>>> +
>>> + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
>>> + return -1;
>>> +
>>> + return fprintf(summary->fp, "\t<testsuite"
>>> + " id=\"%d\""
>>> + " name=\"%s\""
>>> + " hostname=\"localhost\""
>>> + " timestamp=\"%s\""
>>> + " tests=\"%d\""
>>> + " failures=\"%d\""
>>> + " errors=\"%d\">\n",
>>> + idn, name, iso_dt, test_count, fail_count,
>>> error_count);
>>> +}
>>> +
>>> +static int clar_summary_testcase(struct clar_summary *summary,
>>> + const char *name, const char *classname, double elapsed)
>>> +{
>>> + return fprintf(summary->fp,
>>> + "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
>>> + name, classname, elapsed);
>>> +}
>>> +
>>> +static int clar_summary_failure(struct clar_summary *summary,
>>> + const char *type, const char *message, const char *desc)
>>> +{
>>> + return fprintf(summary->fp,
>>> + "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
>>> + type, message, desc);
>>> +}
>>> +
>>> +static int clar_summary_skipped(struct clar_summary *summary)
>>> +{
>>> + return fprintf(summary->fp, "\t\t\t<skipped />\n");
>>> +}
>>> +
>>> +struct clar_summary *clar_summary_init(const char *filename)
>>> +{
>>> + struct clar_summary *summary;
>>> + FILE *fp;
>>> +
>>> + if ((fp = fopen(filename, "w")) == NULL) {
>>> + perror("fopen");
>>> + return NULL;
>>> + }
>>> +
>>> + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
>>> + perror("malloc");
>>> + fclose(fp);
>>> + return NULL;
>>> + }
>>> +
>>> + summary->filename = filename;
>>> + summary->fp = fp;
>>> +
>>> + return summary;
>>> +}
>>> +
>>> +int clar_summary_shutdown(struct clar_summary *summary)
>>> +{
>>> + struct clar_report *report;
>>> + const char *last_suite = NULL;
>>> +
>>> + if (clar_summary_testsuites(summary) < 0)
>>> + goto on_error;
>>> +
>>> + report = _clar.reports;
>>> + while (report != NULL) {
>>> + struct clar_error *error = report->errors;
>>> +
>>> + if (last_suite == NULL || strcmp(last_suite, report->suite) !=
>>> 0) {
>>> + if (clar_summary_testsuite(summary, 0, report->suite,
>>> + report->start, _clar.tests_ran,
>>> _clar.total_errors, 0) < 0)
>>> + goto on_error;
>>> + }
>>> +
>>> + last_suite = report->suite;
>>> +
>>> + clar_summary_testcase(summary, report->test, report->suite,
>>> report->elapsed);
>>> +
>>> + while (error != NULL) {
>>> + if (clar_summary_failure(summary, "assert",
>>> + error->error_msg, error->description) < 0)
>>> + goto on_error;
>>> +
>>> + error = error->next;
>>> + }
>>> +
>>> + if (report->status == CL_TEST_SKIP)
>>> + clar_summary_skipped(summary);
>>> +
>>> + if (clar_summary_close_tag(summary, "testcase", 2) < 0)
>>> + goto on_error;
>>> +
>>> + report = report->next;
>>> +
>>> + if (!report || strcmp(last_suite, report->suite) != 0) {
>>> + if (clar_summary_close_tag(summary, "testsuite", 1) <
>>> 0)
>>> + goto on_error;
>>> + }
>>> + }
>>> +
>>> + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
>>> + fclose(summary->fp) != 0)
>>> + goto on_error;
>>> +
>>> + printf("written summary file to %s\n", summary->filename);
>>> +
>>> + free(summary);
>>> + return 0;
>>> +
>>> +on_error:
>>> + fclose(summary->fp);
>>> + free(summary);
>>> + return -1;
>>> +}
>>> diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
>>> new file mode 100755
>>> index 0000000000..80996ac3e7
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/generate.py
>>> @@ -0,0 +1,266 @@
>>> +#!/usr/bin/env python
>>> +#
>>> +# 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.
>>> +#
>>> +
>>> +from __future__ import with_statement
>>> +from string import Template
>>> +import re, fnmatch, os, sys, codecs, pickle
>>> +
>>> +class Module(object):
>>> + class Template(object):
>>> + def __init__(self, module):
>>> + self.module = module
>>> +
>>> + def _render_callback(self, cb):
>>> + if not cb:
>>> + return ' { NULL, NULL }'
>>> + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
>>> +
>>> + class DeclarationTemplate(Template):
>>> + def render(self):
>>> + out = "\n".join("extern %s;" % cb['declaration'] for cb in
>>> self.module.callbacks) + "\n"
>>> +
>>> + for initializer in self.module.initializers:
>>> + out += "extern %s;\n" % initializer['declaration']
>>> +
>>> + if self.module.cleanup:
>>> + out += "extern %s;\n" % self.module.cleanup['declaration']
>>> +
>>> + return out
>>> +
>>> + class CallbacksTemplate(Template):
>>> + def render(self):
>>> + out = "static const struct clar_func _clar_cb_%s[] = {\n" %
>>> self.module.name
>>> + out += ",\n".join(self._render_callback(cb) for cb in
>>> self.module.callbacks)
>>> + out += "\n};\n"
>>> + return out
>>> +
>>> + class InfoTemplate(Template):
>>> + def render(self):
>>> + templates = []
>>> +
>>> + initializers = self.module.initializers
>>> + if len(initializers) == 0:
>>> + initializers = [ None ]
>>> +
>>> + for initializer in initializers:
>>> + name = self.module.clean_name()
>>> + if initializer and
>>> initializer['short_name'].startswith('initialize_'):
>>> + variant =
>>> initializer['short_name'][len('initialize_'):]
>>> + name += " (%s)" % variant.replace('_', ' ')
>>> +
>>> + template = Template(
>>> + r"""
>>> + {
>>> + "${clean_name}",
>>> + ${initialize},
>>> + ${cleanup},
>>> + ${cb_ptr}, ${cb_count}, ${enabled}
>>> + }"""
>>> + ).substitute(
>>> + clean_name = name,
>>> + initialize = self._render_callback(initializer),
>>> + cleanup = self._render_callback(self.module.cleanup),
>>> + cb_ptr = "_clar_cb_%s" % self.module.name,
>>> + cb_count = len(self.module.callbacks),
>>> + enabled = int(self.module.enabled)
>>> + )
>>> + templates.append(template)
>>> +
>>> + return ','.join(templates)
>>> +
>>> + def __init__(self, name):
>>> + self.name = name
>>> +
>>> + self.mtime = None
>>> + self.enabled = True
>>> + self.modified = False
>>> +
>>> + def clean_name(self):
>>> + return self.name.replace("_", "::")
>>> +
>>> + def _skip_comments(self, text):
>>> + SKIP_COMMENTS_REGEX = re.compile(
>>> + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
>>> + re.DOTALL | re.MULTILINE)
>>> +
>>> + def _replacer(match):
>>> + s = match.group(0)
>>> + return "" if s.startswith('/') else s
>>> +
>>> + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
>>> +
>>> + def parse(self, contents):
>>> + TEST_FUNC_REGEX =
>>> r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
>>> +
>>> + contents = self._skip_comments(contents)
>>> + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
>>> +
>>> + self.callbacks = []
>>> + self.initializers = []
>>> + self.cleanup = None
>>> +
>>> + for (declaration, symbol, short_name) in regex.findall(contents):
>>> + data = {
>>> + "short_name" : short_name,
>>> + "declaration" : declaration,
>>> + "symbol" : symbol
>>> + }
>>> +
>>> + if short_name.startswith('initialize'):
>>> + self.initializers.append(data)
>>> + elif short_name == 'cleanup':
>>> + self.cleanup = data
>>> + else:
>>> + self.callbacks.append(data)
>>> +
>>> + return self.callbacks != []
>>> +
>>> + def refresh(self, path):
>>> + self.modified = False
>>> +
>>> + try:
>>> + st = os.stat(path)
>>> +
>>> + # Not modified
>>> + if st.st_mtime == self.mtime:
>>> + return True
>>> +
>>> + self.modified = True
>>> + self.mtime = st.st_mtime
>>> +
>>> + with codecs.open(path, encoding='utf-8') as fp:
>>> + raw_content = fp.read()
>>> +
>>> + except IOError:
>>> + return False
>>> +
>>> + return self.parse(raw_content)
>>> +
>>> +class TestSuite(object):
>>> +
>>> + def __init__(self, path, output):
>>> + self.path = path
>>> + self.output = output
>>> +
>>> + def should_generate(self, path):
>>> + if not os.path.isfile(path):
>>> + return True
>>> +
>>> + if any(module.modified for module in self.modules.values()):
>>> + return True
>>> +
>>> + return False
>>> +
>>> + def find_modules(self):
>>> + modules = []
>>> + for root, _, files in os.walk(self.path):
>>> + module_root = root[len(self.path):]
>>> + module_root = [c for c in module_root.split(os.sep) if c]
>>> +
>>> + tests_in_module = fnmatch.filter(files, "*.c")
>>> +
>>> + for test_file in tests_in_module:
>>> + full_path = os.path.join(root, test_file)
>>> + module_name = "_".join(module_root +
>>> [test_file[:-2]]).replace("-", "_")
>>> +
>>> + modules.append((full_path, module_name))
>>> +
>>> + return modules
>>> +
>>> + def load_cache(self):
>>> + path = os.path.join(self.output, '.clarcache')
>>> + cache = {}
>>> +
>>> + try:
>>> + fp = open(path, 'rb')
>>> + cache = pickle.load(fp)
>>> + fp.close()
>>> + except (IOError, ValueError):
>>> + pass
>>> +
>>> + return cache
>>> +
>>> + def save_cache(self):
>>> + path = os.path.join(self.output, '.clarcache')
>>> + with open(path, 'wb') as cache:
>>> + pickle.dump(self.modules, cache)
>>> +
>>> + def load(self, force = False):
>>> + module_data = self.find_modules()
>>> + self.modules = {} if force else self.load_cache()
>>> +
>>> + for path, name in module_data:
>>> + if name not in self.modules:
>>> + self.modules[name] = Module(name)
>>> +
>>> + if not self.modules[name].refresh(path):
>>> + del self.modules[name]
>>> +
>>> + def disable(self, excluded):
>>> + for exclude in excluded:
>>> + for module in self.modules.values():
>>> + name = module.clean_name()
>>> + if name.startswith(exclude):
>>> + module.enabled = False
>>> + module.modified = True
>>> +
>>> + def suite_count(self):
>>> + return sum(max(1, len(m.initializers)) for m in
>>> self.modules.values())
>>> +
>>> + def callback_count(self):
>>> + return sum(len(module.callbacks) for module in
>>> self.modules.values())
>>> +
>>> + def write(self):
>>> + output = os.path.join(self.output, 'clar.suite')
>>> +
>>> + if not self.should_generate(output):
>>> + return False
>>> +
>>> + with open(output, 'w') as data:
>>> + modules = sorted(self.modules.values(), key=lambda module:
>>> module.name)
>>> +
>>> + for module in modules:
>>> + t = Module.DeclarationTemplate(module)
>>> + data.write(t.render())
>>> +
>>> + for module in modules:
>>> + t = Module.CallbacksTemplate(module)
>>> + data.write(t.render())
>>> +
>>> + suites = "static struct clar_suite _clar_suites[] = {" +
>>> ','.join(
>>> + Module.InfoTemplate(module).render() for module in modules
>>> + ) + "\n};\n"
>>> +
>>> + data.write(suites)
>>> +
>>> + data.write("static const size_t _clar_suite_count = %d;\n" %
>>> self.suite_count())
>>> + data.write("static const size_t _clar_callback_count = %d;\n" %
>>> self.callback_count())
>>> +
>>> + self.save_cache()
>>> + return True
>>> +
>>> +if __name__ == '__main__':
>>> + from optparse import OptionParser
>>> +
>>> + parser = OptionParser()
>>> + parser.add_option('-f', '--force', action="store_true", dest='force',
>>> default=False)
>>> + parser.add_option('-x', '--exclude', dest='excluded', action='append',
>>> default=[])
>>> + parser.add_option('-o', '--output', dest='output')
>>> +
>>> + options, args = parser.parse_args()
>>> + if len(args) > 1:
>>> + print("More than one path given")
>>> + sys.exit(1)
>>> +
>>> + path = args.pop() if args else '.'
>>> + output = options.output or path
>>> + suite = TestSuite(path, output)
>>> + suite.load(options.force)
>>> + suite.disable(options.excluded)
>>> + if suite.write():
>>> + print("Written `clar.suite` (%d tests in %d suites)" %
>>> (suite.callback_count(), suite.suite_count()))
>>> diff --git a/t/unit-tests/clar/test/.gitignore
>>> b/t/unit-tests/clar/test/.gitignore
>>> new file mode 100644
>>> index 0000000000..a477d0c40c
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/.gitignore
>>> @@ -0,0 +1,4 @@
>>> +clar.suite
>>> +.clarcache
>>> +clar_test
>>> +*.o
>>> diff --git a/t/unit-tests/clar/test/Makefile
>>> b/t/unit-tests/clar/test/Makefile
>>> new file mode 100644
>>> index 0000000000..93c6b2ad32
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/Makefile
>>> @@ -0,0 +1,39 @@
>>> +#
>>> +# 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.
>>> +#
>>> +
>>> +#
>>> +# Set up the path to the clar sources and to the fixtures directory
>>> +#
>>> +# The fixture path needs to be an absolute path so it can be used
>>> +# even after we have chdir'ed into the test directory while testing.
>>> +#
>>> +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
>>> +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
>>> +CLAR_PATH := $(dir $(TEST_DIRECTORY))
>>> +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
>>> +
>>> +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
>>> +
>>> +.PHONY: clean
>>> +
>>> +# list the objects that go into our test
>>> +objects = main.o sample.o
>>> +
>>> +# build the test executable itself
>>> +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
>>> + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
>>> +
>>> +# test object files depend on clar macros
>>> +$(objects) : $(CLAR_PATH)clar.h
>>> +
>>> +# build the clar.suite file of test metadata
>>> +clar.suite:
>>> + python "$(CLAR_PATH)generate.py" .
>>> +
>>> +# remove all generated files
>>> +clean:
>>> + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
>>> diff --git a/t/unit-tests/clar/test/clar_test.h
>>> b/t/unit-tests/clar/test/clar_test.h
>>> new file mode 100644
>>> index 0000000000..0fcaa639aa
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/clar_test.h
>>> @@ -0,0 +1,16 @@
>>> +/*
>>> + * 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/main.c b/t/unit-tests/clar/test/main.c
>>> new file mode 100644
>>> index 0000000000..59e56ad255
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/main.c
>>> @@ -0,0 +1,40 @@
>>> +/*
>>> + * 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_test.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;
>>> +
>>> +#ifdef _WIN32
>>> +int __cdecl main(int argc, char *argv[])
>>> +#else
>>> +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);
>>> +
>>> + /* Your custom cleanup here */
>>> + cl_assert_equal_i(8, global_test_counter);
>>> +
>>> + return ret;
>>> +}
>>> diff --git a/t/unit-tests/clar/test/main.c.sample
>>> b/t/unit-tests/clar/test/main.c.sample
>>> new file mode 100644
>>> index 0000000000..a4d91b72fa
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/main.c.sample
>>> @@ -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_test.h"
>>> +
>>> +/*
>>> + * Minimal main() for clar tests.
>>> + *
>>> + * Modify this with any application specific setup or teardown that you
>>> need.
>>> + * 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, 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
>>> +{
>>> + /* Run the test suite */
>>> + return clar_test(argc, argv);
>>> +}
>>> diff --git a/t/unit-tests/clar/test/resources/test/file
>>> b/t/unit-tests/clar/test/resources/test/file
>>> new file mode 100644
>>> index 0000000000..220f4aa98a
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/resources/test/file
>>> @@ -0,0 +1 @@
>>> +File
>>> diff --git a/t/unit-tests/clar/test/sample.c
>>> b/t/unit-tests/clar/test/sample.c
>>> new file mode 100644
>>> index 0000000000..faa1209262
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/test/sample.c
>>> @@ -0,0 +1,84 @@
>>> +#include "clar_test.h"
>>> +#include <sys/stat.h>
>>> +
>>> +static int file_size(const char *filename)
>>> +{
>>> + struct stat st;
>>> +
>>> + if (stat(filename, &st) == 0)
>>> + return (int)st.st_size;
>>> + return -1;
>>> +}
>>> +
>>> +void test_sample__initialize(void)
>>> +{
>>> + global_test_counter++;
>>> +}
>>> +
>>> +void test_sample__cleanup(void)
>>> +{
>>> + cl_fixture_cleanup("test");
>>> +
>>> + cl_assert(file_size("test/file") == -1);
>>> +}
>>> +
>>> +void test_sample__1(void)
>>> +{
>>> + cl_assert(1);
>>> + cl_must_pass(0); /* 0 == success */
>>> + cl_must_fail(-1); /* <0 == failure */
>>> + cl_must_pass(-1); /* demonstrate a failing call */
>>> +}
>>> +
>>> +void test_sample__2(void)
>>> +{
>>> + cl_fixture_sandbox("test");
>>> +
>>> + cl_assert(file_size("test/nonexistent") == -1);
>>> + cl_assert(file_size("test/file") > 0);
>>> + cl_assert(100 == 101);
>>> +}
>>> +
>>> +void test_sample__strings(void)
>>> +{
>>> + const char *actual = "expected";
>>> + cl_assert_equal_s("expected", actual);
>>> + cl_assert_equal_s_("expected", actual, "second try with annotation");
>>> + cl_assert_equal_s_("mismatched", actual, "this one fails");
>>> +}
>>> +
>>> +void test_sample__strings_with_length(void)
>>> +{
>>> + const char *actual = "expected";
>>> + cl_assert_equal_strn("expected_", actual, 8);
>>> + cl_assert_equal_strn("exactly", actual, 2);
>>> + cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
>>> + cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
>>> +}
>>> +
>>> +void test_sample__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)
>>> +{
>>> + int value = 100;
>>> + cl_assert_equal_i_fmt(022, value, "%04o");
>>> +}
>>> +
>>> +void test_sample__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)
>>> +{
>>> + const char *actual = "expected";
>>> + cl_assert_equal_p(actual, actual); /* pointers to same object */
>>> + cl_assert_equal_p(&actual, actual);
>>> +}
>>
>
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-16 7:04 ` [PATCH v5 2/9] t: import the clar unit testing framework Patrick Steinhardt
2024-08-16 13:37 ` Phillip Wood
@ 2024-08-19 21:21 ` Junio C Hamano
2024-08-19 21:50 ` rsbecker
2024-08-20 12:59 ` Patrick Steinhardt
1 sibling, 2 replies; 172+ messages in thread
From: Junio C Hamano @ 2024-08-19 21:21 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Patrick Steinhardt <ps@pks.im> writes:
> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
> new file mode 100644
> index 0000000000..3fc2c76815
> --- /dev/null
> +++ b/t/unit-tests/clar/clar.c
> @@ -0,0 +1,842 @@
> ...
> +
> +#ifdef _WIN32
> +# include <windows.h>
> +# include <io.h>
> +# include <shellapi.h>
> +# include <direct.h>
This seems to break Windows build
In file included from t/unit-tests/clar/clar.c:22:
D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error: shellapi.h: No such file or directory
89 | #include <shellapi.h>
| ^~~~~~~~~~~~
compilation terminated.
https://github.com/git/git/actions/runs/10459342252/job/28963152995
As we have other topics that I do want to do well in 'seen' before
merging them to 'next', I'd temporarily drop this topic from 'seen'
and push out the rest of the topics.
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-19 21:21 ` Junio C Hamano
@ 2024-08-19 21:50 ` rsbecker
2024-08-19 22:13 ` Junio C Hamano
2024-08-20 12:59 ` Patrick Steinhardt
1 sibling, 1 reply; 172+ messages in thread
From: rsbecker @ 2024-08-19 21:50 UTC (permalink / raw)
To: 'Junio C Hamano', 'Patrick Steinhardt'
Cc: git, 'René Scharfe', 'Kyle Lippincott',
'Phillip Wood', 'Josh Steadmon',
'Edward Thomson'
On Monday, August 19, 2024 5:22 PM, Junio C Hamano wrote:
>Patrick Steinhardt <ps@pks.im> writes:
>
>> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c new
>> file mode 100644 index 0000000000..3fc2c76815
>> --- /dev/null
>> +++ b/t/unit-tests/clar/clar.c
>> @@ -0,0 +1,842 @@
>> ...
>> +
>> +#ifdef _WIN32
>> +# include <windows.h>
>> +# include <io.h>
>> +# include <shellapi.h>
>> +# include <direct.h>
>
>This seems to break Windows build
>
> In file included from t/unit-tests/clar/clar.c:22:
> D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error: shellapi.h:
>No such file or directory
> 89 | #include <shellapi.h>
> | ^~~~~~~~~~~~
> compilation terminated.
>
>https://github.com/git/git/actions/runs/10459342252/job/28963152995
>
>As we have other topics that I do want to do well in 'seen' before merging them to
>'next', I'd temporarily drop this topic from 'seen'
>and push out the rest of the topics.
shellapi.h is not portable. This breaks the NonStop build also.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-19 21:50 ` rsbecker
@ 2024-08-19 22:13 ` Junio C Hamano
2024-08-19 22:38 ` rsbecker
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-19 22:13 UTC (permalink / raw)
To: rsbecker
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon', 'Edward Thomson'
<rsbecker@nexbridge.com> writes:
> On Monday, August 19, 2024 5:22 PM, Junio C Hamano wrote:
>>Patrick Steinhardt <ps@pks.im> writes:
>>
>>> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c new
>>> file mode 100644 index 0000000000..3fc2c76815
>>> --- /dev/null
>>> +++ b/t/unit-tests/clar/clar.c
>>> @@ -0,0 +1,842 @@
>>> ...
>>> +
>>> +#ifdef _WIN32
>>> +# include <windows.h>
>>> +# include <io.h>
>>> +# include <shellapi.h>
>>> +# include <direct.h>
>>
>>This seems to break Windows build
>>
>> In file included from t/unit-tests/clar/clar.c:22:
>> D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error: shellapi.h:
>>No such file or directory
>> 89 | #include <shellapi.h>
>> | ^~~~~~~~~~~~
>> compilation terminated.
>>
>>https://github.com/git/git/actions/runs/10459342252/job/28963152995
>>
>>As we have other topics that I do want to do well in 'seen' before merging them to
>>'next', I'd temporarily drop this topic from 'seen'
>>and push out the rest of the topics.
>
> shellapi.h is not portable. This breaks the NonStop build also.
Even inside "#ifdef _WIN32"?
^ permalink raw reply [flat|nested] 172+ messages in thread
* RE: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-19 22:13 ` Junio C Hamano
@ 2024-08-19 22:38 ` rsbecker
0 siblings, 0 replies; 172+ messages in thread
From: rsbecker @ 2024-08-19 22:38 UTC (permalink / raw)
To: 'Junio C Hamano'
Cc: 'Patrick Steinhardt', git, 'René Scharfe',
'Kyle Lippincott', 'Phillip Wood',
'Josh Steadmon', 'Edward Thomson'
On Monday, August 19, 2024 6:14 PM, Junio C Hamano wrote:
><rsbecker@nexbridge.com> writes:
>
>> On Monday, August 19, 2024 5:22 PM, Junio C Hamano wrote:
>>>Patrick Steinhardt <ps@pks.im> writes:
>>>
>>>> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c new
>>>> file mode 100644 index 0000000000..3fc2c76815
>>>> --- /dev/null
>>>> +++ b/t/unit-tests/clar/clar.c
>>>> @@ -0,0 +1,842 @@
>>>> ...
>>>> +
>>>> +#ifdef _WIN32
>>>> +# include <windows.h>
>>>> +# include <io.h>
>>>> +# include <shellapi.h>
>>>> +# include <direct.h>
>>>
>>>This seems to break Windows build
>>>
>>> In file included from t/unit-tests/clar/clar.c:22:
>>> D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error:
>shellapi.h:
>>>No such file or directory
>>> 89 | #include <shellapi.h>
>>> | ^~~~~~~~~~~~
>>> compilation terminated.
>>>
>>>https://github.com/git/git/actions/runs/10459342252/job/28963152995
>>>
>>>As we have other topics that I do want to do well in 'seen' before
>>>merging them to 'next', I'd temporarily drop this topic from 'seen'
>>>and push out the rest of the topics.
>>
>> shellapi.h is not portable. This breaks the NonStop build also.
>
>Even inside "#ifdef _WIN32"?
Good point, but I did not see that in the prior review. My apologies.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 2/9] t: import the clar unit testing framework
2024-08-19 21:21 ` Junio C Hamano
2024-08-19 21:50 ` rsbecker
@ 2024-08-20 12:59 ` Patrick Steinhardt
1 sibling, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 12:59 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
On Mon, Aug 19, 2024 at 02:21:44PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
> > new file mode 100644
> > index 0000000000..3fc2c76815
> > --- /dev/null
> > +++ b/t/unit-tests/clar/clar.c
> > @@ -0,0 +1,842 @@
> > ...
> > +
> > +#ifdef _WIN32
> > +# include <windows.h>
> > +# include <io.h>
> > +# include <shellapi.h>
> > +# include <direct.h>
>
> This seems to break Windows build
>
> In file included from t/unit-tests/clar/clar.c:22:
> D:/git-sdk-64-minimal/mingw64/include/windows.h:89:10: fatal error: shellapi.h: No such file or directory
> 89 | #include <shellapi.h>
> | ^~~~~~~~~~~~
> compilation terminated.
>
> https://github.com/git/git/actions/runs/10459342252/job/28963152995
>
> As we have other topics that I do want to do well in 'seen' before
> merging them to 'next', I'd temporarily drop this topic from 'seen'
> and push out the rest of the topics.
Makes sense. I (or rather Dscho, thanks!) will have a deeper look at
Windows and then also rebase the branch to fix conflicts with the
updated tests in "master".
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v5 3/9] t/clar: fix compatibility with NonStop
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 1/9] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 2/9] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 4/9] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
` (6 subsequent siblings)
9 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f3525..e25057b7c4 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v5 4/9] Makefile: fix sparse dependency on GENERATED_H
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (2 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 3/9] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 5/9] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
` (5 subsequent siblings)
9 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
The "check" Makefile target is essentially an alias around the "sparse"
target. The one difference though is that it will tell users to instead
run the "test" target in case they do not have sparse(1) installed, as
chances are high that they wanted to execute the test suite rather than
doing semantic checks.
But even though the "check" target ultimately just ends up executing
`make sparse`, it still depends on our generated headers. This does not
make any sense though: they are irrelevant for the "test" target advice,
and if these headers are required for the "sparse" target they must be
declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as
concurrent builds may then end up generating the headers and running
sparse concurrently. Instead, we make them a dependency of the specific
objects. While that is overly broad, it does ensure correct ordering.
The alternative, specifying which file depends on what generated header
explicitly, feels rather unmaintainable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 674b0ac4e1..0736d3c88e 100644
--- a/Makefile
+++ b/Makefile
@@ -3253,7 +3253,7 @@ check-sha1:: t/helper/test-tool$X
SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
-$(SP_OBJ): %.sp: %.c %.o
+$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
-Wsparse-error \
$(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3294,7 +3294,7 @@ style:
git clang-format --style file --diff --extensions c,h
.PHONY: check
-check: $(GENERATED_H)
+check:
@if sparse; \
then \
echo >&2 "Use 'make sparse' instead"; \
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v5 5/9] Makefile: make hdr-check depend on generated headers
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (3 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 4/9] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 6/9] Makefile: do not use sparse on third-party sources Patrick Steinhardt
` (4 subsequent siblings)
9 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
The "hdr-check" Makefile target compiles each of our headers as a
standalone code unit to ensure that they are not missing any type
declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework,
which will have the effect that some headers start depending on
generated ones. While we could declare that dependency explicitly, it
does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects
depend on all of our generated headers. While again overly broad, it is
easy to maintain and generating headers is not an expensive thing to do
anyway.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 0736d3c88e..5384bf8104 100644
--- a/Makefile
+++ b/Makefile
@@ -3283,7 +3283,7 @@ HCC = $(HCO:hco=hcc)
@echo '#include "git-compat-util.h"' >$@
@echo '#include "$<"' >>$@
-$(HCO): %.hco: %.hcc FORCE
+$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE
$(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $<
.PHONY: hdr-check $(HCO)
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v5 6/9] Makefile: do not use sparse on third-party sources
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (4 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 5/9] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 7/9] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (3 subsequent siblings)
9 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
We have several third-party sources in our codebase that we have
imported from upstream projects. These sources are mostly excluded from
our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 5384bf8104..377ea7e144 100644
--- a/Makefile
+++ b/Makefile
@@ -3251,7 +3251,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
check-sha1:: t/helper/test-tool$X
t/helper/test-sha1.sh
-SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
+SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS)))
+SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC))
$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v5 7/9] Makefile: wire up the clar unit testing framework
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (5 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 6/9] Makefile: do not use sparse on third-party sources Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 7:04 ` [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (2 subsequent siblings)
9 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 36 +++++++++++++++++++++---
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 18 ++++++++++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 107 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c2..6687bd6db4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 377ea7e144..e6206ca8c7 100644
--- a/Makefile
+++ b/Makefile
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a
GENERATED_H += command-list.h
GENERATED_H += config-list.h
GENERATED_H += hook-list.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar.suite
.PHONY: generated-hdrs
generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2713,6 +2720,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3215,7 +3223,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3647,7 +3655,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3703,6 +3711,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3862,7 +3871,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b7..131ffd778f 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec..d0632ec7f9 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 0000000000..ab71ce6c9f
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 0000000000..32a81299e9
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,18 @@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **argv_copy;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+ ALLOC_ARRAY(argv_copy, argc + 2);
+ COPY_ARRAY(argv_copy, argv, argc);
+ argv_copy[argc++] = "-t";
+ argv_copy[argc] = NULL;
+
+ ret = clar_test(argc, (char **) argv_copy);
+
+ free(argv_copy);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 0000000000..66ec2387cc
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (6 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 7/9] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-08-16 7:04 ` Patrick Steinhardt
2024-08-16 13:38 ` Phillip Wood
2024-08-16 7:05 ` [PATCH v5 9/9] t/unit-tests: convert ctype " Patrick Steinhardt
2024-08-16 13:37 ` [PATCH v5 0/9] Introduce clar testing framework Phillip Wood
9 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:04 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-strvec.c => strvec.c} | 119 ++++++++++----------------
t/unit-tests/unit-test.c | 3 +-
3 files changed, 45 insertions(+), 79 deletions(-)
rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
diff --git a/Makefile b/Makefile
index e6206ca8c7..b0c9a79c55 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1353,7 +1354,6 @@ UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/strvec.c
similarity index 54%
rename from t/unit-tests/t-strvec.c
rename to t/unit-tests/strvec.c
index fa1a041469..d11ed0f28d 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/strvec.c
@@ -1,52 +1,46 @@
-#include "test-lib.h"
+#include "unit-test.h"
#include "strbuf.h"
#include "strvec.h"
#define check_strvec(vec, ...) \
do { \
const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
} while (0)
-static void t_static_init(void)
+void test_strvec__init(void)
{
struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_dynamic_init(void)
+void test_strvec__dynamic_init(void)
{
struct strvec vec;
strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_clear(void)
+void test_strvec__clear(void)
{
struct strvec vec = STRVEC_INIT;
strvec_push(&vec, "foo");
strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
}
-static void t_push(void)
+void test_strvec__push(void)
{
struct strvec vec = STRVEC_INIT;
@@ -59,7 +53,7 @@ static void t_push(void)
strvec_clear(&vec);
}
-static void t_pushf(void)
+void test_strvec__pushf(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushf(&vec, "foo: %d", 1);
@@ -67,7 +61,7 @@ static void t_pushf(void)
strvec_clear(&vec);
}
-static void t_pushl(void)
+void test_strvec__pushl(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -75,7 +69,7 @@ static void t_pushl(void)
strvec_clear(&vec);
}
-static void t_pushv(void)
+void test_strvec__pushv(void)
{
const char *strings[] = {
"foo", "bar", "baz", NULL,
@@ -88,7 +82,7 @@ static void t_pushv(void)
strvec_clear(&vec);
}
-static void t_replace_at_head(void)
+void test_strvec__replace_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -97,7 +91,7 @@ static void t_replace_at_head(void)
strvec_clear(&vec);
}
-static void t_replace_at_tail(void)
+void test_strvec__replace_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -106,7 +100,7 @@ static void t_replace_at_tail(void)
strvec_clear(&vec);
}
-static void t_replace_in_between(void)
+void test_strvec__replace_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -115,7 +109,7 @@ static void t_replace_in_between(void)
strvec_clear(&vec);
}
-static void t_replace_with_substring(void)
+void test_strvec__replace_with_substring(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", NULL);
@@ -124,7 +118,7 @@ static void t_replace_with_substring(void)
strvec_clear(&vec);
}
-static void t_remove_at_head(void)
+void test_strvec__remove_at_head(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -133,7 +127,7 @@ static void t_remove_at_head(void)
strvec_clear(&vec);
}
-static void t_remove_at_tail(void)
+void test_strvec__remove_at_tail(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -142,7 +136,7 @@ static void t_remove_at_tail(void)
strvec_clear(&vec);
}
-static void t_remove_in_between(void)
+void test_strvec__remove_in_between(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -151,7 +145,7 @@ static void t_remove_in_between(void)
strvec_clear(&vec);
}
-static void t_pop_empty_array(void)
+void test_strvec__pop_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pop(&vec);
@@ -159,7 +153,7 @@ static void t_pop_empty_array(void)
strvec_clear(&vec);
}
-static void t_pop_non_empty_array(void)
+void test_strvec__pop_non_empty_array(void)
{
struct strvec vec = STRVEC_INIT;
strvec_pushl(&vec, "foo", "bar", "baz", NULL);
@@ -168,7 +162,7 @@ static void t_pop_non_empty_array(void)
strvec_clear(&vec);
}
-static void t_split_empty_string(void)
+void test_strvec__split_empty_string(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "");
@@ -176,7 +170,7 @@ static void t_split_empty_string(void)
strvec_clear(&vec);
}
-static void t_split_single_item(void)
+void test_strvec__split_single_item(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo");
@@ -184,7 +178,7 @@ static void t_split_single_item(void)
strvec_clear(&vec);
}
-static void t_split_multiple_items(void)
+void test_strvec__split_multiple_items(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo bar baz");
@@ -192,7 +186,7 @@ static void t_split_multiple_items(void)
strvec_clear(&vec);
}
-static void t_split_whitespace_only(void)
+void test_strvec__split_whitespace_only(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, " \t\n");
@@ -200,7 +194,7 @@ static void t_split_whitespace_only(void)
strvec_clear(&vec);
}
-static void t_split_multiple_consecutive_whitespaces(void)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
{
struct strvec vec = STRVEC_INIT;
strvec_split(&vec, "foo\n\t bar");
@@ -208,7 +202,7 @@ static void t_split_multiple_consecutive_whitespaces(void)
strvec_clear(&vec);
}
-static void t_detach(void)
+void test_strvec__detach(void)
{
struct strvec vec = STRVEC_INIT;
const char **detached;
@@ -216,40 +210,13 @@ static void t_detach(void)
strvec_push(&vec, "foo");
detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
free((char *) detached[0]);
free(detached);
}
-
-int cmd_main(int argc, const char **argv)
-{
- TEST(t_static_init(), "static initialization");
- TEST(t_dynamic_init(), "dynamic initialization");
- TEST(t_clear(), "clear");
- TEST(t_push(), "push");
- TEST(t_pushf(), "pushf");
- TEST(t_pushl(), "pushl");
- TEST(t_pushv(), "pushv");
- TEST(t_replace_at_head(), "replace at head");
- TEST(t_replace_in_between(), "replace in between");
- TEST(t_replace_at_tail(), "replace at tail");
- TEST(t_replace_with_substring(), "replace with substring");
- TEST(t_remove_at_head(), "remove at head");
- TEST(t_remove_in_between(), "remove in between");
- TEST(t_remove_at_tail(), "remove at tail");
- TEST(t_pop_empty_array(), "pop with empty array");
- TEST(t_pop_non_empty_array(), "pop with non-empty array");
- TEST(t_split_empty_string(), "split empty string");
- TEST(t_split_single_item(), "split single item");
- TEST(t_split_multiple_items(), "split multiple items");
- TEST(t_split_whitespace_only(), "split whitespace only");
- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
- TEST(t_detach(), "detach");
- return test_done();
-}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 32a81299e9..82b7635e6a 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
int ret;
/* Append the "-t" flag such that the tests generate TAP output. */
- ALLOC_ARRAY(argv_copy, argc + 2);
+ ALLOC_ARRAY(argv_copy, argc + 1);
COPY_ARRAY(argv_copy, argv, argc);
argv_copy[argc++] = "-t";
- argv_copy[argc] = NULL;
ret = clar_test(argc, (char **) argv_copy);
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
2024-08-16 7:04 ` [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-16 13:38 ` Phillip Wood
2024-08-16 16:11 ` Junio C Hamano
0 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-16 13:38 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Hi Patrick
On 16/08/2024 08:04, Patrick Steinhardt wrote:
> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
> index 32a81299e9..82b7635e6a 100644
> --- a/t/unit-tests/unit-test.c
> +++ b/t/unit-tests/unit-test.c
> @@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
> int ret;
>
> /* Append the "-t" flag such that the tests generate TAP output. */
> - ALLOC_ARRAY(argv_copy, argc + 2);
> + ALLOC_ARRAY(argv_copy, argc + 1);
> COPY_ARRAY(argv_copy, argv, argc);
> argv_copy[argc++] = "-t";
> - argv_copy[argc] = NULL;
I'm confused by this - it looks like it belongs in the previous patch
but why are we deleting the line that adds the terminating NULL to argv
in the first place?
Thanks
Phillip
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
2024-08-16 13:38 ` Phillip Wood
@ 2024-08-16 16:11 ` Junio C Hamano
2024-08-20 12:59 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-08-16 16:11 UTC (permalink / raw)
To: Phillip Wood
Cc: Patrick Steinhardt, git, René Scharfe, Kyle Lippincott,
Phillip Wood, Josh Steadmon, rsbecker, Edward Thomson
Phillip Wood <phillip.wood123@gmail.com> writes:
> Hi Patrick
>
> On 16/08/2024 08:04, Patrick Steinhardt wrote:
>> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
>> index 32a81299e9..82b7635e6a 100644
>> --- a/t/unit-tests/unit-test.c
>> +++ b/t/unit-tests/unit-test.c
>> @@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
>> int ret;
>> /* Append the "-t" flag such that the tests generate TAP
>> output. */
>> - ALLOC_ARRAY(argv_copy, argc + 2);
>> + ALLOC_ARRAY(argv_copy, argc + 1);
>> COPY_ARRAY(argv_copy, argv, argc);
>> argv_copy[argc++] = "-t";
>> - argv_copy[argc] = NULL;
>
> I'm confused by this - it looks like it belongs in the previous patch
> but why are we deleting the line that adds the terminating NULL to
> argv in the first place?
I agree with you that it belongs to the previous step. clar_test(),
unlike the usual convention used by main(), works solely with argc
to tell where the list of argument ends, without requiring argv[] to
be an NULL terminated array. I do not know if clar _promises_ that
as the calling convention to its users, but if it does so, and if we
want to take advantage of that promise and leave the terminating
NULL out of argv[], we should do so from the beginning.
A NULL-terminated argv[], as long as we make sure argc only counts
the real arguments without counting that terminating NULL, would not
harm the clar_test(argc, argv) call, so I actually do not mind the
way this caller was originally written, with terminating NULL. It
looks a lot more familiar, even though we may be wasting memory at
the end of argv[] array.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar
2024-08-16 16:11 ` Junio C Hamano
@ 2024-08-20 12:59 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 12:59 UTC (permalink / raw)
To: Junio C Hamano
Cc: Phillip Wood, git, René Scharfe, Kyle Lippincott,
Phillip Wood, Josh Steadmon, rsbecker, Edward Thomson
On Fri, Aug 16, 2024 at 09:11:36AM -0700, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> > Hi Patrick
> >
> > On 16/08/2024 08:04, Patrick Steinhardt wrote:
> >> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
> >> index 32a81299e9..82b7635e6a 100644
> >> --- a/t/unit-tests/unit-test.c
> >> +++ b/t/unit-tests/unit-test.c
> >> @@ -6,10 +6,9 @@ int cmd_main(int argc, const char **argv)
> >> int ret;
> >> /* Append the "-t" flag such that the tests generate TAP
> >> output. */
> >> - ALLOC_ARRAY(argv_copy, argc + 2);
> >> + ALLOC_ARRAY(argv_copy, argc + 1);
> >> COPY_ARRAY(argv_copy, argv, argc);
> >> argv_copy[argc++] = "-t";
> >> - argv_copy[argc] = NULL;
> >
> > I'm confused by this - it looks like it belongs in the previous patch
> > but why are we deleting the line that adds the terminating NULL to
> > argv in the first place?
>
> I agree with you that it belongs to the previous step. clar_test(),
> unlike the usual convention used by main(), works solely with argc
> to tell where the list of argument ends, without requiring argv[] to
> be an NULL terminated array. I do not know if clar _promises_ that
> as the calling convention to its users, but if it does so, and if we
> want to take advantage of that promise and leave the terminating
> NULL out of argv[], we should do so from the beginning.
Agreed.
> A NULL-terminated argv[], as long as we make sure argc only counts
> the real arguments without counting that terminating NULL, would not
> harm the clar_test(argc, argv) call, so I actually do not mind the
> way this caller was originally written, with terminating NULL. It
> looks a lot more familiar, even though we may be wasting memory at
> the end of argv[] array.
Okay. It's not necessary, but I don't mind it much, either.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (7 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-16 7:05 ` Patrick Steinhardt
2024-08-16 13:38 ` Phillip Wood
2024-08-18 6:39 ` Junio C Hamano
2024-08-16 13:37 ` [PATCH v5 0/9] Introduce clar testing framework Phillip Wood
9 siblings, 2 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-16 7:05 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Convert the ctype tests to use the new clar unit testing framework.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------
2 files changed, 59 insertions(+), 14 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
diff --git a/Makefile b/Makefile
index b0c9a79c55..000eca9f86 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-mem-pool
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 71%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index d6ac1fe678..311df3a539 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,18 +1,12 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- int skip = test__run_begin(); \
- if (!skip) { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ for (int i = 0; i < 256; i++) \
+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -33,21 +27,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
--
2.46.0.46.g406f326d27.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
2024-08-16 7:05 ` [PATCH v5 9/9] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-08-16 13:38 ` Phillip Wood
2024-08-20 12:59 ` Patrick Steinhardt
2024-08-18 6:39 ` Junio C Hamano
1 sibling, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-16 13:38 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Hi Patrick
On 16/08/2024 08:05, Patrick Steinhardt wrote:
> #define TEST_CHAR_CLASS(class, string) do { \
> size_t len = ARRAY_SIZE(string) - 1 + \
> BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
> BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
> - int skip = test__run_begin(); \
> - if (!skip) { \
> - for (int i = 0; i < 256; i++) { \
> - if (!check_int(class(i), ==, !!memchr(string, i, len)))\
> - test_msg(" i: 0x%02x", i); \
> - } \
> - check(!class(EOF)); \
> - } \
> - test__run_end(!skip, TEST_LOCATION(), #class " works"); \
> + for (int i = 0; i < 256; i++) \
> + cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
If this fails how are we supposed to know which character it was checking?
Thanks
Phillip
> + cl_assert(!class(EOF)); \
> } while (0)
>
> #define DIGIT "0123456789"
> @@ -33,21 +27,72 @@
> "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
> "\x7f"
>
> -int cmd_main(int argc, const char **argv) {
> +void test_ctype__isspace(void)
> +{
> TEST_CHAR_CLASS(isspace, " \n\r\t");
> +}
> +
> +void test_ctype__isdigit(void)
> +{
> TEST_CHAR_CLASS(isdigit, DIGIT);
> +}
> +
> +void test_ctype__isalpha(void)
> +{
> TEST_CHAR_CLASS(isalpha, LOWER UPPER);
> +}
> +
> +void test_ctype__isalnum(void)
> +{
> TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
> +}
> +
> +void test_ctype__is_glob_special(void)
> +{
> TEST_CHAR_CLASS(is_glob_special, "*?[\\");
> +}
> +
> +void test_ctype__is_regex_special(void)
> +{
> TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
> +}
> +
> +void test_ctype__is_pathspec_magic(void)
> +{
> TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
> +}
> +
> +void test_ctype__isascii(void)
> +{
> TEST_CHAR_CLASS(isascii, ASCII);
> +}
> +
> +void test_ctype__islower(void)
> +{
> TEST_CHAR_CLASS(islower, LOWER);
> +}
> +
> +void test_ctype__isupper(void)
> +{
> TEST_CHAR_CLASS(isupper, UPPER);
> +}
> +
> +void test_ctype__iscntrl(void)
> +{
> TEST_CHAR_CLASS(iscntrl, CNTRL);
> +}
> +
> +void test_ctype__ispunct(void)
> +{
> TEST_CHAR_CLASS(ispunct, PUNCT);
> +}
> +
> +void test_ctype__isxdigit(void)
> +{
> TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
> - TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
> +}
>
> - return test_done();
> +void test_ctype__isprint(void)
> +{
> + TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
> }
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
2024-08-16 13:38 ` Phillip Wood
@ 2024-08-20 12:59 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 12:59 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson
On Fri, Aug 16, 2024 at 02:38:30PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 16/08/2024 08:05, Patrick Steinhardt wrote:
> > #define TEST_CHAR_CLASS(class, string) do { \
> > size_t len = ARRAY_SIZE(string) - 1 + \
> > BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
> > BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
> > - int skip = test__run_begin(); \
> > - if (!skip) { \
> > - for (int i = 0; i < 256; i++) { \
> > - if (!check_int(class(i), ==, !!memchr(string, i, len)))\
> > - test_msg(" i: 0x%02x", i); \
> > - } \
> > - check(!class(EOF)); \
> > - } \
> > - test__run_end(!skip, TEST_LOCATION(), #class " works"); \
> > + for (int i = 0; i < 256; i++) \
> > + cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
>
> If this fails how are we supposed to know which character it was checking?
>
> Thanks
I'll introduce a new function `cl_failf()` that allows us to print
information like this.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar
2024-08-16 7:05 ` [PATCH v5 9/9] t/unit-tests: convert ctype " Patrick Steinhardt
2024-08-16 13:38 ` Phillip Wood
@ 2024-08-18 6:39 ` Junio C Hamano
1 sibling, 0 replies; 172+ messages in thread
From: Junio C Hamano @ 2024-08-18 6:39 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Patrick Steinhardt <ps@pks.im> writes:
> Convert the ctype tests to use the new clar unit testing framework.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> Makefile | 2 +-
> t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++------
> 2 files changed, 59 insertions(+), 14 deletions(-)
> rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
In 'next' we use the if_test stuff to simplify the ctype unit tests.
This gives a good example to illustrate the strengths and weakness
of these approaches. I resolved the conflict in 'seen' with this
topic in favor of clar tests so that it becomes easier to compare.
> #define TEST_CHAR_CLASS(class, string) do { \
> size_t len = ARRAY_SIZE(string) - 1 + \
> BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
> BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
> - int skip = test__run_begin(); \
> - if (!skip) { \
> - for (int i = 0; i < 256; i++) { \
> - if (!check_int(class(i), ==, !!memchr(string, i, len)))\
> - test_msg(" i: 0x%02x", i); \
> - } \
> - check(!class(EOF)); \
> - } \
> - test__run_end(!skip, TEST_LOCATION(), #class " works"); \
> + for (int i = 0; i < 256; i++) \
> + cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
> + cl_assert(!class(EOF)); \
> } while (0)
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 0/9] Introduce clar testing framework
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
` (8 preceding siblings ...)
2024-08-16 7:05 ` [PATCH v5 9/9] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-08-16 13:37 ` Phillip Wood
2024-08-20 12:59 ` Patrick Steinhardt
9 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-16 13:37 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson
Hi Patrick
On 16/08/2024 08:04, Patrick Steinhardt wrote:
> Hi,
>
> this is the fifth version of my patch series that introduces the clar
> testing framework for our unit tests.
Thanks for working on this, I'm broadly in favor of this change. I
like the way it keeps each test as a function and adds automatic test
registration with support for setup and teardown functions. I am keen
though to keep an emphasis on good diagnostic messages when tests
fail. Looking at the conversions in this series all of the test_msg()
lines that provide useful debugging context are removed. I'm not sure
using yaml to report errors rather than human readable messages is an
improvement either.
I wonder if we want to either improve the assertions offered by clar
or write our own. I find the names of the cl_assert_equal_?()
functions are a bit cumbersome. The aim of the check_* names was to
try and be both concise and descriptive. Adding our own check_* macros
on top of clar would also make it easier to port our existing tests.
Here are some thought having read through the assertion and error
reporting code:
- As I think you've pointed out elsewhere there are no equivalents
for check_int(a, <|<=|>|>=, b) so we're forced to use cl_assert()
and forego the better diagnostic messages that come from a
dedicated comparison macro. We should fix this as a priority.
- cl_assert_equal_i() casts its arguments to int whereas check_int()
and check_uint() are careful to avoid truncation and keep the
original signedness (if that's a word). I think that's unlikely to
be a problem with our current test but could trip us up in the
future.
- cl_assert_equal_s() prints each argument as-is. This means
that it passes NULL arguments through to snprintf() which is
undefined according to the C standard. Compare this to check_str()
that is NULL safe and is careful to escape control characters and
add delimiters to the beginning and end of the string to make it
obvious when a string contains leading or trailing whitespace.
- The cl_assert_equal_?() macros lack type safety for the arguments
being compared as they are wrappers around a variadic function.
That could be fixed by having each macros wrap a dedicated
function that wraps clar__fail().
- There is no equivalent of test_todo() to mark assertions that are
expected to fail. We're not using that yet in our tests but our
experience with the integration tests suggests that we are likely
to want this in the future.
- To me the "sandbox" feature is mis-named as it does not provide any
confinement. It is instead a useful mechanism for running a test in
a temporary directory created from a template.
- There are no checks for failing memory allocations - the return
value of calloc() and strdup() are used without checking for NULL.
- The use of longjmp is a bit of a double edged sword as it makes it
easy to leak resources on test failures.
Best Wishes
Phillip
> Changes compared to v4:
>
> - The whitespace fixes have been merged upstream, so I've updated the
> embedded copy of clar and dropped the subsequent patch that fixed
> them in our copy. The NonStop compatibility fixes have not yet been
> merged as the pull request needs some more work.
>
> - Both "clar-decls.h" and "clar.suite" are now part of GENERATED_H.
> This brings removal of these files via "make clean" for free.
>
> - The "sparse" target already depends on GENERATED_H, but in a broken
> way. I've fixed that in a new commit.
>
> - The "sparse" target no longer checks external sources, including the
> clar sources.
>
> - The "hdr-check" target now depends on GENERATED_H, as well. This
> avoids having to manually wire up dependencies on generated headers
> per file, which seems rather unmaintainable to me.
>
> With this, the "hdr-check" and "sparse" targets all work on my machine
> now.
>
> Thanks!
>
> Patrick
>
> Patrick Steinhardt (9):
> t: do not pass GIT_TEST_OPTS to unit tests with prove
> t: import the clar unit testing framework
> t/clar: fix compatibility with NonStop
> Makefile: fix sparse dependency on GENERATED_H
> Makefile: make hdr-check depend on generated headers
> Makefile: do not use sparse on third-party sources
> Makefile: wire up the clar unit testing framework
> t/unit-tests: convert strvec tests to use clar
> t/unit-tests: convert ctype tests to use clar
>
> .gitignore | 1 +
> Documentation/technical/unit-tests.txt | 2 +
> Makefile | 53 +-
> t/Makefile | 4 +-
> t/run-test.sh | 2 +-
> t/unit-tests/.gitignore | 2 +
> t/unit-tests/clar-generate.awk | 50 ++
> t/unit-tests/clar/.github/workflows/ci.yml | 23 +
> t/unit-tests/clar/COPYING | 15 +
> t/unit-tests/clar/README.md | 329 ++++++++
> t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
> t/unit-tests/clar/clar.h | 173 +++++
> t/unit-tests/clar/clar/fixtures.h | 50 ++
> t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
> t/unit-tests/clar/clar/print.h | 211 ++++++
> t/unit-tests/clar/clar/sandbox.h | 159 ++++
> t/unit-tests/clar/clar/summary.h | 143 ++++
> t/unit-tests/clar/generate.py | 266 +++++++
> t/unit-tests/clar/test/.gitignore | 4 +
> t/unit-tests/clar/test/Makefile | 39 +
> t/unit-tests/clar/test/clar_test.h | 16 +
> t/unit-tests/clar/test/main.c | 40 +
> t/unit-tests/clar/test/main.c.sample | 27 +
> t/unit-tests/clar/test/resources/test/file | 1 +
> t/unit-tests/clar/test/sample.c | 84 ++
> t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
> t/unit-tests/{t-strvec.c => strvec.c} | 119 ++-
> t/unit-tests/unit-test.c | 17 +
> t/unit-tests/unit-test.h | 3 +
> 29 files changed, 3166 insertions(+), 102 deletions(-)
> create mode 100644 t/unit-tests/clar-generate.awk
> create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
> create mode 100644 t/unit-tests/clar/COPYING
> create mode 100644 t/unit-tests/clar/README.md
> create mode 100644 t/unit-tests/clar/clar.c
> create mode 100644 t/unit-tests/clar/clar.h
> create mode 100644 t/unit-tests/clar/clar/fixtures.h
> create mode 100644 t/unit-tests/clar/clar/fs.h
> create mode 100644 t/unit-tests/clar/clar/print.h
> create mode 100644 t/unit-tests/clar/clar/sandbox.h
> create mode 100644 t/unit-tests/clar/clar/summary.h
> create mode 100755 t/unit-tests/clar/generate.py
> create mode 100644 t/unit-tests/clar/test/.gitignore
> create mode 100644 t/unit-tests/clar/test/Makefile
> create mode 100644 t/unit-tests/clar/test/clar_test.h
> create mode 100644 t/unit-tests/clar/test/main.c
> create mode 100644 t/unit-tests/clar/test/main.c.sample
> create mode 100644 t/unit-tests/clar/test/resources/test/file
> create mode 100644 t/unit-tests/clar/test/sample.c
> rename t/unit-tests/{t-ctype.c => ctype.c} (71%)
> rename t/unit-tests/{t-strvec.c => strvec.c} (54%)
> create mode 100644 t/unit-tests/unit-test.c
> create mode 100644 t/unit-tests/unit-test.h
>
> Range-diff against v4:
> 1: 086dd728a7 ! 1: 832dc0496f t: do not pass GIT_TEST_OPTS to unit tests with prove
> @@ Commit message
> environment variable. Like this, we can conditionally forward it to our
> test scripts, only.
>
> + Signed-off-by: Patrick Steinhardt <ps@pks.im>
> +
> ## t/Makefile ##
> @@ t/Makefile: failed:
> test -z "$$failed" || $(MAKE) $$failed
> 2: 5c22e0b3b9 ! 2: 3690607933 t: import the clar unit testing framework
> @@ Metadata
> ## Commit message ##
> t: import the clar unit testing framework
>
> - Import the clar unit testing framework at commit faa8419 (Merge pull
> - request #93 from clar-test/ethomson/fixtures, 2023-12-14). The framework
> + Import the clar unit testing framework at commit 1516124 (Merge pull
> + request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
> will be wired up in subsequent commits.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> @@ t/unit-tests/clar/clar/fs.h (new)
> + ERROR_PATH_NOT_FOUND == last_error)
> + return 0;
> +
> -+ Sleep(RM_RETRY_DELAY * retries * retries);
> ++ Sleep(RM_RETRY_DELAY * retries * retries);
> + }
> + while (retries++ <= RM_RETRY_COUNT);
> +
> @@ t/unit-tests/clar/clar/sandbox.h (new)
> + static const size_t var_count = 5;
> + static const char *env_vars[] = {
> + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
> -+ };
> ++ };
> +
> -+ size_t i;
> ++ size_t i;
> +
> + for (i = 0; i < var_count; ++i) {
> + const char *env = getenv(env_vars[i]);
> @@ t/unit-tests/clar/clar/sandbox.h (new)
> +{
> + return _clar_path;
> +}
> -+
>
> ## t/unit-tests/clar/clar/summary.h (new) ##
> @@
> @@ t/unit-tests/clar/generate.py (new)
> + suite.disable(options.excluded)
> + if suite.write():
> + print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
> -+
>
> ## t/unit-tests/clar/test/.gitignore (new) ##
> @@
> @@ t/unit-tests/clar/test/.gitignore (new)
> +.clarcache
> +clar_test
> +*.o
> -+
>
> ## t/unit-tests/clar/test/Makefile (new) ##
> @@
> 4: 75e097dfa4 = 3: db53673294 t/clar: fix compatibility with NonStop
> 3: e0f99874cc ! 4: b6199c88dd t/clar: fix whitespace errors
> @@ Metadata
> Author: Patrick Steinhardt <ps@pks.im>
>
> ## Commit message ##
> - t/clar: fix whitespace errors
> -
> - Fix whitespace errors in the clar that make git-apply(1) unhappy. This
> - has been cherry-picked from the upstream pull request at [1].
> -
> - [1]: https://github.com/clar-test/clar/pull/97
> + Makefile: fix sparse dependency on GENERATED_H
> +
> + The "check" Makefile target is essentially an alias around the "sparse"
> + target. The one difference though is that it will tell users to instead
> + run the "test" target in case they do not have sparse(1) installed, as
> + chances are high that they wanted to execute the test suite rather than
> + doing semantic checks.
> +
> + But even though the "check" target ultimately just ends up executing
> + `make sparse`, it still depends on our generated headers. This does not
> + make any sense though: they are irrelevant for the "test" target advice,
> + and if these headers are required for the "sparse" target they must be
> + declared as a dependency on the aliased target, not the alias.
> +
> + But even moving the dependency to the "sparse" target is wrong, as
> + concurrent builds may then end up generating the headers and running
> + sparse concurrently. Instead, we make them a dependency of the specific
> + objects. While that is overly broad, it does ensure correct ordering.
> + The alternative, specifying which file depends on what generated header
> + explicitly, feels rather unmaintainable.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>
> - ## t/unit-tests/clar/clar/fs.h ##
> -@@ t/unit-tests/clar/clar/fs.h: fs_rm_wait(WCHAR *_wpath)
> - ERROR_PATH_NOT_FOUND == last_error)
> - return 0;
> -
> -- Sleep(RM_RETRY_DELAY * retries * retries);
> -+ Sleep(RM_RETRY_DELAY * retries * retries);
> - }
> - while (retries++ <= RM_RETRY_COUNT);
> + ## Makefile ##
> +@@ Makefile: check-sha1:: t/helper/test-tool$X
>
> -
> - ## t/unit-tests/clar/clar/sandbox.h ##
> -@@ t/unit-tests/clar/clar/sandbox.h: find_tmp_path(char *buffer, size_t length)
> - static const size_t var_count = 5;
> - static const char *env_vars[] = {
> - "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
> -- };
> -+ };
> + SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
>
> -- size_t i;
> -+ size_t i;
> +-$(SP_OBJ): %.sp: %.c %.o
> ++$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
> + $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
> + -Wsparse-error \
> + $(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
> +@@ Makefile: style:
> + git clang-format --style file --diff --extensions c,h
>
> - for (i = 0; i < var_count; ++i) {
> - const char *env = getenv(env_vars[i]);
> -@@ t/unit-tests/clar/clar/sandbox.h: const char *clar_sandbox_path(void)
> - {
> - return _clar_path;
> - }
> --
> -
> - ## t/unit-tests/clar/generate.py ##
> -@@ t/unit-tests/clar/generate.py: def write(self):
> - suite.disable(options.excluded)
> - if suite.write():
> - print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
> --
> -
> - ## t/unit-tests/clar/test/.gitignore ##
> -@@ t/unit-tests/clar/test/.gitignore: clar.suite
> - .clarcache
> - clar_test
> - *.o
> --
> + .PHONY: check
> +-check: $(GENERATED_H)
> ++check:
> + @if sparse; \
> + then \
> + echo >&2 "Use 'make sparse' instead"; \
> -: ---------- > 5: 06364b2b72 Makefile: make hdr-check depend on generated headers
> -: ---------- > 6: 88ea94ce16 Makefile: do not use sparse on third-party sources
> 5: 5b8a64ae79 ! 7: 05bcb5bef6 Makefile: wire up the clar unit testing framework
> @@ .gitignore
> /bin-wrappers/
>
> ## Makefile ##
> +@@ Makefile: REFTABLE_TEST_LIB = reftable/libreftable_test.a
> + GENERATED_H += command-list.h
> + GENERATED_H += config-list.h
> + GENERATED_H += hook-list.h
> ++GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h
> ++GENERATED_H += $(UNIT_TEST_DIR)/clar.suite
> +
> + .PHONY: generated-hdrs
> + generated-hdrs: $(GENERATED_H)
> @@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
> THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
> THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
> @@ Makefile: endif
>
> bin-wrappers/%: wrap-for-bin.sh
> $(call mkdir_p_parent_template)
> -@@ Makefile: CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(LIB_H))
> - HCO = $(patsubst %.h,%.hco,$(CHK_HDRS))
> - HCC = $(HCO:hco=hcc)
> -
> -+$(UNIT_TEST_DIR)/unit-test.hcc: $(UNIT_TEST_DIR)/unit-test.h $(UNIT_TEST_DIR)/clar-decls.h
> - %.hcc: %.h
> - @echo '#include "git-compat-util.h"' >$@
> - @echo '#include "$<"' >>$@
> @@ Makefile: endif
>
> artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
> @@ Makefile: cocciclean:
>
> clean: profile-clean coverage-clean cocciclean
> $(RM) -r .build $(UNIT_TEST_BIN)
> -+ $(RM) GIT-TEST-SUITES $(UNIT_TEST_DIR)/clar.suite $(UNIT_TEST_DIR)/clar-decls.h
> ++ $(RM) GIT-TEST-SUITES
> $(RM) po/git.pot po/git-core.pot
> $(RM) git.res
> $(RM) $(OBJECTS)
> 6: bc4e23d666 = 8: 8f56b4d626 t/unit-tests: convert strvec tests to use clar
> 7: 0a7fe8775a = 9: ca09d19fd5 t/unit-tests: convert ctype tests to use clar
>
> base-commit: 406f326d271e0bacecdb00425422c5fa3f314930
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 0/9] Introduce clar testing framework
2024-08-16 13:37 ` [PATCH v5 0/9] Introduce clar testing framework Phillip Wood
@ 2024-08-20 12:59 ` Patrick Steinhardt
2024-08-28 15:15 ` phillip.wood123
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 12:59 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson
On Fri, Aug 16, 2024 at 02:37:34PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 16/08/2024 08:04, Patrick Steinhardt wrote:
> > Hi,
> >
> > this is the fifth version of my patch series that introduces the clar
> > testing framework for our unit tests.
>
> Thanks for working on this, I'm broadly in favor of this change. I
> like the way it keeps each test as a function and adds automatic test
> registration with support for setup and teardown functions. I am keen
> though to keep an emphasis on good diagnostic messages when tests
> fail. Looking at the conversions in this series all of the test_msg()
> lines that provide useful debugging context are removed. I'm not sure
> using yaml to report errors rather than human readable messages is an
> improvement either.
>
> I wonder if we want to either improve the assertions offered by clar
> or write our own. I find the names of the cl_assert_equal_?()
> functions are a bit cumbersome. The aim of the check_* names was to
> try and be both concise and descriptive. Adding our own check_* macros
> on top of clar would also make it easier to port our existing tests.
>
> Here are some thought having read through the assertion and error
> reporting code:
>
> - As I think you've pointed out elsewhere there are no equivalents
> for check_int(a, <|<=|>|>=, b) so we're forced to use cl_assert()
> and forego the better diagnostic messages that come from a
> dedicated comparison macro. We should fix this as a priority.
Agreed, this one also feels rather limiting to me. Are you okay with me
doing this as a follow-up in case this series lands?
> - cl_assert_equal_i() casts its arguments to int whereas check_int()
> and check_uint() are careful to avoid truncation and keep the
> original signedness (if that's a word). I think that's unlikely to
> be a problem with our current test but could trip us up in the
> future.
Yeah. If it ever becomes a problem we can likely just introduce
something like `cl_assert_equal_u()` to achieve the same for unsigned.
Both should probably end up casting to `intmax_t` and `uintmax_t`,
respectively.
> - cl_assert_equal_s() prints each argument as-is. This means
> that it passes NULL arguments through to snprintf() which is
> undefined according to the C standard. Compare this to check_str()
> that is NULL safe and is careful to escape control characters and
> add delimiters to the beginning and end of the string to make it
> obvious when a string contains leading or trailing whitespace.
Good point indeed, and something I'm happy to fix upstream.
> - The cl_assert_equal_?() macros lack type safety for the arguments
> being compared as they are wrappers around a variadic function.
> That could be fixed by having each macros wrap a dedicated
> function that wraps clar__fail().
Some of them do indeed, others generate issues. I don't think we have to
have dedicated functions, but could do something about this with
`__attribute__((format (printf, ...)))`.
> - There is no equivalent of test_todo() to mark assertions that are
> expected to fail. We're not using that yet in our tests but our
> experience with the integration tests suggests that we are likely
> to want this in the future.
Heh, funny that you mention this. I had this discussion some 6 years ago
I think, where I also mentioned that this should exist as a feature. In
any case, I agree.
> - To me the "sandbox" feature is mis-named as it does not provide any
> confinement. It is instead a useful mechanism for running a test in
> a temporary directory created from a template.
I guess we'll either just have to not use it or ignore that it's named a
bit awkwardly. Changing this in clar probably wouldn't work well because
other projects may depend on it.
> - There are no checks for failing memory allocations - the return
> value of calloc() and strdup() are used without checking for NULL.
I'll commit to fixing this upstream if this lands.
> - The use of longjmp is a bit of a double edged sword as it makes it
> easy to leak resources on test failures.
I have to say that this is one of the best features of the clar to me.
The current test framework we use doesn't, which in theory requires you
to always `return` whenever there was an error. But that results in code
that is both awful to read and write, so for most of the tests simply
don't bother at all. And consequently, the tests are quite likely to
cause segfaults once one of the checks fails because we didn't abort
running the testcase, but things are broken.
In practice, I'd claim that you don't typically care all that much about
memory leaks once your basic assertions start to fail.
So, things that need addressing and that I'm happy to do as follow ups:
- Introduce functions that compare integers.
- Improve type safety of the `cl_assert_equal_?()` macros.
- Adapt `cl_assert_equal_s()` to handle NULL pointers.
- Introduce checks for failing memory allocations.
Nice to have would be support for known-failing tests.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v5 0/9] Introduce clar testing framework
2024-08-20 12:59 ` Patrick Steinhardt
@ 2024-08-28 15:15 ` phillip.wood123
0 siblings, 0 replies; 172+ messages in thread
From: phillip.wood123 @ 2024-08-28 15:15 UTC (permalink / raw)
To: Patrick Steinhardt, phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson
Hi Patrick
On 20/08/2024 13:59, Patrick Steinhardt wrote:
> On Fri, Aug 16, 2024 at 02:37:34PM +0100, Phillip Wood wrote:
>> Hi Patrick
>>
>> On 16/08/2024 08:04, Patrick Steinhardt wrote:
>>
>> - As I think you've pointed out elsewhere there are no equivalents
>> for check_int(a, <|<=|>|>=, b) so we're forced to use cl_assert()
>> and forego the better diagnostic messages that come from a
>> dedicated comparison macro. We should fix this as a priority.
>
> Agreed, this one also feels rather limiting to me. Are you okay with me
> doing this as a follow-up in case this series lands?
Yes
>> - cl_assert_equal_i() casts its arguments to int whereas check_int()
>> and check_uint() are careful to avoid truncation and keep the
>> original signedness (if that's a word). I think that's unlikely to
>> be a problem with our current test but could trip us up in the
>> future.
>
> Yeah. If it ever becomes a problem we can likely just introduce
> something like `cl_assert_equal_u()` to achieve the same for unsigned.
> Both should probably end up casting to `intmax_t` and `uintmax_t`,
> respectively.
Supporting wider arguments would make sense. At the moment
clar__assert_equal() does not support PRIiMAX, only the non-standard PRIuZ.
>> - cl_assert_equal_s() prints each argument as-is. This means
>> that it passes NULL arguments through to snprintf() which is
>> undefined according to the C standard. Compare this to check_str()
>> that is NULL safe and is careful to escape control characters and
>> add delimiters to the beginning and end of the string to make it
>> obvious when a string contains leading or trailing whitespace.
>
> Good point indeed, and something I'm happy to fix upstream.
That's great
>> - The cl_assert_equal_?() macros lack type safety for the arguments
>> being compared as they are wrappers around a variadic function.
>> That could be fixed by having each macros wrap a dedicated
>> function that wraps clar__fail().
>
> Some of them do indeed, others generate issues. I don't think we have to
> have dedicated functions, but could do something about this with
> `__attribute__((format (printf, ...)))`.
I wondered about suggesting '__attribute__((format (printf, ...)))' but
we'd need to double up the format argument in order to use it which is
kind of messy. At the moment we pass "%i" with two integers.
>> - There is no equivalent of test_todo() to mark assertions that are
>> expected to fail. We're not using that yet in our tests but our
>> experience with the integration tests suggests that we are likely
>> to want this in the future.
>
> Heh, funny that you mention this. I had this discussion some 6 years ago
> I think, where I also mentioned that this should exist as a feature. In
> any case, I agree.
Excellent!
>> - To me the "sandbox" feature is mis-named as it does not provide any
>> confinement. It is instead a useful mechanism for running a test in
>> a temporary directory created from a template.
>
> I guess we'll either just have to not use it or ignore that it's named a
> bit awkwardly. Changing this in clar probably wouldn't work well because
> other projects may depend on it.
Yes it's probably too late to rename it. I think being able to create a
test directory from a template directory could be useful, we just need
to be mindful that the test code is not confined by a sandbox.
>> - There are no checks for failing memory allocations - the return
>> value of calloc() and strdup() are used without checking for NULL.
>
> I'll commit to fixing this upstream if this lands.
Great
>> - The use of longjmp is a bit of a double edged sword as it makes it
>> easy to leak resources on test failures.
>
> I have to say that this is one of the best features of the clar to me.
> The current test framework we use doesn't, which in theory requires you
> to always `return` whenever there was an error. But that results in code
> that is both awful to read and write, so for most of the tests simply
> don't bother at all. And consequently, the tests are quite likely to
> cause segfaults once one of the checks fails because we didn't abort
> running the testcase, but things are broken.
I thought that the tests took care to bail out early where it made
sense. Sometimes it is useful to continue for example when checking an
strbuf we might want to check alloc and len before bailing out. We're
probably not losing much by not doing that though.
> In practice, I'd claim that you don't typically care all that much about
> memory leaks once your basic assertions start to fail.
I tend to agree. I was thinking more about exhausting fds and cleaning
up files but that's probably not a big issue in practice.
> So, things that need addressing and that I'm happy to do as follow ups:
>
> - Introduce functions that compare integers.
>
> - Improve type safety of the `cl_assert_equal_?()` macros.
>
> - Adapt `cl_assert_equal_s()` to handle NULL pointers.
>
> - Introduce checks for failing memory allocations.
>
> Nice to have would be support for known-failing tests.
This all sounds good to me
Sorry for missing this mail earlier.
Phillip
> Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v6 00/13] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (8 preceding siblings ...)
2024-08-16 7:04 ` [PATCH v5 0/9] " Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 01/13] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (13 more replies)
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
11 siblings, 14 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi,
this is another version of my patch series that introduces the clar
testing framework for our C unit tests.
Changes compared to v5:
- Rebased on top of `master` to fix some merge conflicts. This series
is now built on bb9c16bd4f (The sixth batch, 2024-08-19).
- Introduce `cl_failf()` as a nice way to print formatted error
messages.
- NULL-terminate the argv array again and move the changes into the
correct patch.
- Most of the changes were done by Dscho to port clar over to Windows,
including CMake build instructions to make this usable with MSVC. I
plan to upstream these changes when this series lands in Git.
- Introduce CMake support, also by Dscho.
Thanks for all the feedback, and thanks to Dscho for helping out with
porting this to Windows.
GitHub pipeline: https://github.com/git/git/actions/runs/10472008177/
GitLab pipeline: https://gitlab.com/gitlab-org/git/-/pipelines/1419946971
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64
clar(win32): avoid compile error due to unused `fs_copy()`
clar: stop including `shellapi.h` unnecessarily
clar: add CMake support
Patrick Steinhardt (9):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
contrib/buildsystems/CMakeLists.txt | 53 ++
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 70 +-
t/unit-tests/strvec.c | 222 ++++++
t/unit-tests/t-strvec.c | 211 ------
t/unit-tests/unit-test.c | 18 +
t/unit-tests/unit-test.h | 10 +
31 files changed, 3409 insertions(+), 235 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (70%)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v5:
1: 832dc0496fb = 1: e48a6461137 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: 36906079330 = 2: 1710e9f9ff7 t: import the clar unit testing framework
3: db53673294e = 3: 5c21aa87aa2 t/clar: fix compatibility with NonStop
-: ----------- > 4: 06d2bce0d82 clar: avoid compile error with mingw-w64
-: ----------- > 5: f88b3421a09 clar(win32): avoid compile error due to unused `fs_copy()`
-: ----------- > 6: 5fb4c55be33 clar: stop including `shellapi.h` unnecessarily
4: b6199c88dd7 = 7: e0dcbd5ca83 Makefile: fix sparse dependency on GENERATED_H
5: 06364b2b722 = 8: 77a03f8df70 Makefile: make hdr-check depend on generated headers
6: 88ea94ce16c = 9: c91dd7327e3 Makefile: do not use sparse on third-party sources
7: 05bcb5bef6c = 10: 115c15aa9ae Makefile: wire up the clar unit testing framework
8: 8f56b4d6264 ! 11: b3b8df04872 t/unit-tests: convert strvec tests to use clar
@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
-@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
- UNIT_TEST_PROGRAMS += t-reftable-record
+@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-record
+ UNIT_TEST_PROGRAMS += t-reftable-tree
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
- ## t/unit-tests/t-strvec.c => t/unit-tests/strvec.c ##
+ ## t/unit-tests/strvec.c (new) ##
@@
--#include "test-lib.h"
+#include "unit-test.h"
- #include "strbuf.h"
- #include "strvec.h"
-
- #define check_strvec(vec, ...) \
- do { \
- const char *expect[] = { __VA_ARGS__ }; \
-- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
-- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
-- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
-- check_uint((vec)->nr, <=, (vec)->alloc)) { \
-- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
-- if (!check_str((vec)->v[i], expect[i])) { \
-- test_msg(" i: %"PRIuMAX, \
-- (uintmax_t)i); \
-- break; \
-- } \
-- } \
-- } \
++#include "strbuf.h"
++#include "strvec.h"
++
++#define check_strvec(vec, ...) \
++ do { \
++ const char *expect[] = { __VA_ARGS__ }; \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
- } while (0)
-
--static void t_static_init(void)
++ } while (0)
++
+void test_strvec__init(void)
- {
- struct strvec vec = STRVEC_INIT;
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++{
++ struct strvec vec = STRVEC_INIT;
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
- }
-
--static void t_dynamic_init(void)
++}
++
+void test_strvec__dynamic_init(void)
- {
- struct strvec vec;
- strvec_init(&vec);
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++{
++ struct strvec vec;
++ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
- }
-
--static void t_clear(void)
++}
++
+void test_strvec__clear(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_push(&vec, "foo");
- strvec_clear(&vec);
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_push(&vec, "foo");
++ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
- }
-
--static void t_push(void)
++}
++
+void test_strvec__push(void)
- {
- struct strvec vec = STRVEC_INIT;
-
-@@ t/unit-tests/strvec.c: static void t_push(void)
- strvec_clear(&vec);
- }
-
--static void t_pushf(void)
++{
++ struct strvec vec = STRVEC_INIT;
++
++ strvec_push(&vec, "foo");
++ check_strvec(&vec, "foo", NULL);
++
++ strvec_push(&vec, "bar");
++ check_strvec(&vec, "foo", "bar", NULL);
++
++ strvec_clear(&vec);
++}
++
+void test_strvec__pushf(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushf(&vec, "foo: %d", 1);
-@@ t/unit-tests/strvec.c: static void t_pushf(void)
- strvec_clear(&vec);
- }
-
--static void t_pushl(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushf(&vec, "foo: %d", 1);
++ check_strvec(&vec, "foo: 1", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pushl(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_pushl(void)
- strvec_clear(&vec);
- }
-
--static void t_pushv(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ check_strvec(&vec, "foo", "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pushv(void)
- {
- const char *strings[] = {
- "foo", "bar", "baz", NULL,
-@@ t/unit-tests/strvec.c: static void t_pushv(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_at_head(void)
++{
++ const char *strings[] = {
++ "foo", "bar", "baz", NULL,
++ };
++ struct strvec vec = STRVEC_INIT;
++
++ strvec_pushv(&vec, strings);
++ check_strvec(&vec, "foo", "bar", "baz", NULL);
++
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_at_head(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_at_head(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_at_tail(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_replace(&vec, 0, "replaced");
++ check_strvec(&vec, "replaced", "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_at_tail(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_at_tail(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_in_between(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_replace(&vec, 2, "replaced");
++ check_strvec(&vec, "foo", "bar", "replaced", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_in_between(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_in_between(void)
- strvec_clear(&vec);
- }
-
--static void t_replace_with_substring(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_replace(&vec, 1, "replaced");
++ check_strvec(&vec, "foo", "replaced", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__replace_with_substring(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", NULL);
-@@ t/unit-tests/strvec.c: static void t_replace_with_substring(void)
- strvec_clear(&vec);
- }
-
--static void t_remove_at_head(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", NULL);
++ strvec_replace(&vec, 0, vec.v[0] + 1);
++ check_strvec(&vec, "oo", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__remove_at_head(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_remove_at_head(void)
- strvec_clear(&vec);
- }
-
--static void t_remove_at_tail(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_remove(&vec, 0);
++ check_strvec(&vec, "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__remove_at_tail(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_remove_at_tail(void)
- strvec_clear(&vec);
- }
-
--static void t_remove_in_between(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_remove(&vec, 2);
++ check_strvec(&vec, "foo", "bar", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__remove_in_between(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_remove_in_between(void)
- strvec_clear(&vec);
- }
-
--static void t_pop_empty_array(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_remove(&vec, 1);
++ check_strvec(&vec, "foo", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pop_empty_array(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pop(&vec);
-@@ t/unit-tests/strvec.c: static void t_pop_empty_array(void)
- strvec_clear(&vec);
- }
-
--static void t_pop_non_empty_array(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pop(&vec);
++ check_strvec(&vec, NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__pop_non_empty_array(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
-@@ t/unit-tests/strvec.c: static void t_pop_non_empty_array(void)
- strvec_clear(&vec);
- }
-
--static void t_split_empty_string(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
++ strvec_pop(&vec);
++ check_strvec(&vec, "foo", "bar", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_empty_string(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "");
-@@ t/unit-tests/strvec.c: static void t_split_empty_string(void)
- strvec_clear(&vec);
- }
-
--static void t_split_single_item(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "");
++ check_strvec(&vec, NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_single_item(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo");
-@@ t/unit-tests/strvec.c: static void t_split_single_item(void)
- strvec_clear(&vec);
- }
-
--static void t_split_multiple_items(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "foo");
++ check_strvec(&vec, "foo", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_multiple_items(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo bar baz");
-@@ t/unit-tests/strvec.c: static void t_split_multiple_items(void)
- strvec_clear(&vec);
- }
-
--static void t_split_whitespace_only(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "foo bar baz");
++ check_strvec(&vec, "foo", "bar", "baz", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_whitespace_only(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, " \t\n");
-@@ t/unit-tests/strvec.c: static void t_split_whitespace_only(void)
- strvec_clear(&vec);
- }
-
--static void t_split_multiple_consecutive_whitespaces(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, " \t\n");
++ check_strvec(&vec, NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__split_multiple_consecutive_whitespaces(void)
- {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo\n\t bar");
-@@ t/unit-tests/strvec.c: static void t_split_multiple_consecutive_whitespaces(void)
- strvec_clear(&vec);
- }
-
--static void t_detach(void)
++{
++ struct strvec vec = STRVEC_INIT;
++ strvec_split(&vec, "foo\n\t bar");
++ check_strvec(&vec, "foo", "bar", NULL);
++ strvec_clear(&vec);
++}
++
+void test_strvec__detach(void)
- {
- struct strvec vec = STRVEC_INIT;
- const char **detached;
-@@ t/unit-tests/strvec.c: static void t_detach(void)
- strvec_push(&vec, "foo");
-
- detached = strvec_detach(&vec);
-- check_str(detached[0], "foo");
-- check_pointer_eq(detached[1], NULL);
++{
++ struct strvec vec = STRVEC_INIT;
++ const char **detached;
++
++ strvec_push(&vec, "foo");
++
++ detached = strvec_detach(&vec);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
-
-- check_pointer_eq(vec.v, empty_strvec);
-- check_uint(vec.nr, ==, 0);
-- check_uint(vec.alloc, ==, 0);
++
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
-
- free((char *) detached[0]);
- free(detached);
- }
++
++ free((char *) detached[0]);
++ free(detached);
++}
+
+ ## t/unit-tests/t-strvec.c (deleted) ##
+@@
+-#include "test-lib.h"
+-#include "strbuf.h"
+-#include "strvec.h"
+-
+-#define check_strvec(vec, ...) \
+- do { \
+- const char *expect[] = { __VA_ARGS__ }; \
+- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
+- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
+- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
+- check_uint((vec)->nr, <=, (vec)->alloc)) { \
+- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
+- if (!check_str((vec)->v[i], expect[i])) { \
+- test_msg(" i: %"PRIuMAX, \
+- (uintmax_t)i); \
+- break; \
+- } \
+- } \
+- } \
+- } while (0)
-
-int cmd_main(int argc, const char **argv)
-{
-- TEST(t_static_init(), "static initialization");
-- TEST(t_dynamic_init(), "dynamic initialization");
-- TEST(t_clear(), "clear");
-- TEST(t_push(), "push");
-- TEST(t_pushf(), "pushf");
-- TEST(t_pushl(), "pushl");
-- TEST(t_pushv(), "pushv");
-- TEST(t_replace_at_head(), "replace at head");
-- TEST(t_replace_in_between(), "replace in between");
-- TEST(t_replace_at_tail(), "replace at tail");
-- TEST(t_replace_with_substring(), "replace with substring");
-- TEST(t_remove_at_head(), "remove at head");
-- TEST(t_remove_in_between(), "remove in between");
-- TEST(t_remove_at_tail(), "remove at tail");
-- TEST(t_pop_empty_array(), "pop with empty array");
-- TEST(t_pop_non_empty_array(), "pop with non-empty array");
-- TEST(t_split_empty_string(), "split empty string");
-- TEST(t_split_single_item(), "split single item");
-- TEST(t_split_multiple_items(), "split multiple items");
-- TEST(t_split_whitespace_only(), "split whitespace only");
-- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
-- TEST(t_detach(), "detach");
+- if_test ("static initialization") {
+- struct strvec vec = STRVEC_INIT;
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+- }
+-
+- if_test ("dynamic initialization") {
+- struct strvec vec;
+- strvec_init(&vec);
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+- }
+-
+- if_test ("clear") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_push(&vec, "foo");
+- strvec_clear(&vec);
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+- }
+-
+- if_test ("push") {
+- struct strvec vec = STRVEC_INIT;
+-
+- strvec_push(&vec, "foo");
+- check_strvec(&vec, "foo", NULL);
+-
+- strvec_push(&vec, "bar");
+- check_strvec(&vec, "foo", "bar", NULL);
+-
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pushf") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushf(&vec, "foo: %d", 1);
+- check_strvec(&vec, "foo: 1", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pushl") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- check_strvec(&vec, "foo", "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pushv") {
+- const char *strings[] = {
+- "foo", "bar", "baz", NULL,
+- };
+- struct strvec vec = STRVEC_INIT;
+-
+- strvec_pushv(&vec, strings);
+- check_strvec(&vec, "foo", "bar", "baz", NULL);
+-
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace at head") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_replace(&vec, 0, "replaced");
+- check_strvec(&vec, "replaced", "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace at tail") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_replace(&vec, 2, "replaced");
+- check_strvec(&vec, "foo", "bar", "replaced", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace in between") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_replace(&vec, 1, "replaced");
+- check_strvec(&vec, "foo", "replaced", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("replace with substring") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", NULL);
+- strvec_replace(&vec, 0, vec.v[0] + 1);
+- check_strvec(&vec, "oo", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("remove at head") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_remove(&vec, 0);
+- check_strvec(&vec, "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("remove at tail") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_remove(&vec, 2);
+- check_strvec(&vec, "foo", "bar", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("remove in between") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_remove(&vec, 1);
+- check_strvec(&vec, "foo", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pop with empty array") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pop(&vec);
+- check_strvec(&vec, NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("pop with non-empty array") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+- strvec_pop(&vec);
+- check_strvec(&vec, "foo", "bar", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split empty string") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "");
+- check_strvec(&vec, NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split single item") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "foo");
+- check_strvec(&vec, "foo", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split multiple items") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "foo bar baz");
+- check_strvec(&vec, "foo", "bar", "baz", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split whitespace only") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, " \t\n");
+- check_strvec(&vec, NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("split multiple consecutive whitespaces") {
+- struct strvec vec = STRVEC_INIT;
+- strvec_split(&vec, "foo\n\t bar");
+- check_strvec(&vec, "foo", "bar", NULL);
+- strvec_clear(&vec);
+- }
+-
+- if_test ("detach") {
+- struct strvec vec = STRVEC_INIT;
+- const char **detached;
+-
+- strvec_push(&vec, "foo");
+-
+- detached = strvec_detach(&vec);
+- check_str(detached[0], "foo");
+- check_pointer_eq(detached[1], NULL);
+-
+- check_pointer_eq(vec.v, empty_strvec);
+- check_uint(vec.nr, ==, 0);
+- check_uint(vec.alloc, ==, 0);
+-
+- free((char *) detached[0]);
+- free(detached);
+- }
+-
- return test_done();
-}
-
- ## t/unit-tests/unit-test.c ##
-@@ t/unit-tests/unit-test.c: int cmd_main(int argc, const char **argv)
- int ret;
-
- /* Append the "-t" flag such that the tests generate TAP output. */
-- ALLOC_ARRAY(argv_copy, argc + 2);
-+ ALLOC_ARRAY(argv_copy, argc + 1);
- COPY_ARRAY(argv_copy, argv, argc);
- argv_copy[argc++] = "-t";
-- argv_copy[argc] = NULL;
-
- ret = clar_test(argc, (char **) argv_copy);
-
9: ca09d19fd51 ! 12: 1ac2e48a7f2 t/unit-tests: convert ctype tests to use clar
@@ Commit message
t/unit-tests: convert ctype tests to use clar
Convert the ctype tests to use the new clar unit testing framework.
+ Introduce a new function `cl_failf()` that allows us to print a
+ formatted error message, which we can use to point out which of the
+ characters was classified incorrectly. This results in output like this
+ on failure:
+
+ # start of suite 1: ctype
+ ok 1 - ctype::isspace
+ not ok 2 - ctype::isdigit
+ ---
+ reason: |
+ Test failed.
+ 0x61 is classified incorrectly
+ at:
+ file: 't/unit-tests/ctype.c'
+ line: 38
+ function: 'test_ctype__isdigit'
+ ---
+ ok 3 - ctype::isalpha
+ ok 4 - ctype::isalnum
+ ok 5 - ctype::is_glob_special
+ ok 6 - ctype::is_regex_special
+ ok 7 - ctype::is_pathspec_magic
+ ok 8 - ctype::isascii
+ ok 9 - ctype::islower
+ ok 10 - ctype::isupper
+ ok 11 - ctype::iscntrl
+ ok 12 - ctype::ispunct
+ ok 13 - ctype::isxdigit
+ ok 14 - ctype::isprint
Signed-off-by: Patrick Steinhardt <ps@pks.im>
@@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
- UNIT_TEST_PROGRAMS += t-mem-pool
+ UNIT_TEST_PROGRAMS += t-hashmap
## t/unit-tests/t-ctype.c => t/unit-tests/ctype.c ##
@@
@@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
-- int skip = test__run_begin(); \
-- if (!skip) { \
+- if_test (#class " works") { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
-- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
+ for (int i = 0; i < 256; i++) \
-+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
++ if (class(i) != !!memchr(string, i, len)) \
++ cl_failf("0x%02x is classified incorrectly", i); \
+ cl_assert(!class(EOF)); \
} while (0)
@@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
+
+ ## t/unit-tests/unit-test.h ##
+@@
+ #include "git-compat-util.h"
+ #include "clar/clar.h"
+ #include "clar-decls.h"
++#include "strbuf.h"
++
++#define cl_failf(fmt, ...) do { \
++ char *desc = xstrfmt(fmt, __VA_ARGS__); \
++ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
++ free(desc); \
++} while (0)
-: ----------- > 13: 131036c398e clar: add CMake support
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v6 01/13] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 02/13] t: import the clar unit testing framework Patrick Steinhardt
` (12 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06fb..d2212de0b78 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b4..63328ac630c 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 02/13] t: import the clar unit testing framework
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 01/13] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-28 13:16 ` Phillip Wood
2024-08-20 14:02 ` [PATCH v6 03/13] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (11 subsequent siblings)
13 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Import the clar unit testing framework at commit 1516124 (Merge pull
request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 153 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2943 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb19..5a432b7b29c 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index a87e18b317d..28742a60964 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 00000000000..b1ac2de460a
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 00000000000..8983817f0c9
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 00000000000..a8961c5f10f
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 00000000000..3fc2c768158
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 00000000000..8c22382bd56
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 00000000000..6ec6423484d
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 00000000000..3e39890bd3e
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 00000000000..c17e2f693bd
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 00000000000..7c177f35258
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,153 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 00000000000..4dd352e28b8
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 00000000000..80996ac3e71
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 00000000000..a477d0c40ca
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 00000000000..93c6b2ad32c
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 00000000000..0fcaa639aa8
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 00000000000..59e56ad255b
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 00000000000..a4d91b72fa8
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 00000000000..220f4aa98a7
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 00000000000..faa1209262f
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v6 02/13] t: import the clar unit testing framework
2024-08-20 14:02 ` [PATCH v6 02/13] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-28 13:16 ` Phillip Wood
2024-09-03 7:45 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-28 13:16 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 20/08/2024 15:02, Patrick Steinhardt wrote:
> Import the clar unit testing framework at commit 1516124 (Merge pull
> request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
> will be wired up in subsequent commits.
I would be good to explain why we're doing this so we have a record in
the project history. A summary of the advantages and disadvantages of
using clar vs our current unit test framework would be helpful as well.
> diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
> [...]
> +static void
> +clar_parse_args(int argc, char **argv)
> +{
> [...]
> + case 'q':
> + _clar.report_errors_only = 1;
> + break;
I think this option is incompatible with TAP output as when TAP output
is selected the first error is printed as part of clar_print_ontest()
and clar_print_errors() is a no-op. We should error out if it is given
with '-t'
> +void clar__fail(
> + const char *file,
> + const char *function,
> + size_t line,
> + const char *error_msg,
> + const char *description,
> + int should_abort)
> +{
> + struct clar_error *error = calloc(1, sizeof(struct clar_error));
clar seems to take an inconsistent approach to memory allocation errors.
Here "error" is de-referenced without checking if it is NULL and yet in
other places there are checks for NULL after calling calloc() or strdup().
> +void clar__assert_equal(
> + const char *file,
> + const char *function,
> + size_t line,
> + const char *err,
> + int should_abort,
> + const char *fmt,
> + ...)
> +{
> + va_list args;
> + char buf[4096];
> + int is_equal = 1;
> +
> + va_start(args, fmt);
> +
> + if (!strcmp("%s", fmt)) {
> + const char *s1 = va_arg(args, const char *);
> + const char *s2 = va_arg(args, const char *);
> + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
> +
> + if (!is_equal) {
> + if (s1 && s2) {
> + int pos;
> + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
> + /* find differing byte offset */;
> + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
> + s1, s2, pos);
s1 and s2 are passed to snprintf without checking if they are NULL. As
'snprintf(buf, sizeof(buf), "%s", NULL)' is undefined we should check
for NULL here. The use of variadic arguments means there is no type
checking so if one is checking the contents of an strbuf and writes
cl_assert_equal_s(buf, "expect");
instead of
cl_assert_equal_s(buf->buf, "expect");
the compiler does not warn you. This is a regression compared to
check_str(). We can address this by having cl_assert_equal_s() wrap a
function that takes two strings. Another regression is that if the
string contains control characters they are printed verbatim.
> diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
> [...]
> +#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
The (int) casts here mean that we'll silently truncate longer arguments
and suppress any compiler warnings about comparing incompatible types.
> diff --git a/t/unit-tests/clar/clar/print.h
b/t/unit-tests/clar/clar/print.h
> [...]
> +static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
> +{
> + const struct clar_error *error = _clar.last_report->errors;
> +
> + (void)test_name;
> + (void)test_number;
> +
> + switch(status) {
> + case CL_TEST_OK:
> + printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
> + break;
> + 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 (error->description)
> + printf(" %s\n", error->description);
> +
> + printf(" at:\n");
> + printf(" file: '"); print_escaped(error->file); printf("'\n");
> + printf(" line: %" PRIuZ "\n", error->line_number);
> + printf(" function: '%s'\n", error->function);
> + printf(" ---\n");
If a test calls cl_warning() only the message from the first call to
that function is printed and any message from later calls to
cl_warning() or clar__fail() is suppressed when TAP output is selected.
Best Wishes
Phillip
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 02/13] t: import the clar unit testing framework
2024-08-28 13:16 ` Phillip Wood
@ 2024-09-03 7:45 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 7:45 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Wed, Aug 28, 2024 at 02:16:52PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 20/08/2024 15:02, Patrick Steinhardt wrote:
> > Import the clar unit testing framework at commit 1516124 (Merge pull
> > request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
> > will be wired up in subsequent commits.
>
> I would be good to explain why we're doing this so we have a record in the
> project history. A summary of the advantages and disadvantages of using clar
> vs our current unit test framework would be helpful as well.
Will do.
> > diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
> > [...]
> > +static void
> > +clar_parse_args(int argc, char **argv)
> > +{
> > [...]
> > + case 'q':
> > + _clar.report_errors_only = 1;
> > + break;
>
> I think this option is incompatible with TAP output as when TAP output is
> selected the first error is printed as part of clar_print_ontest() and
> clar_print_errors() is a no-op. We should error out if it is given with '-t'
Yup. I'll address this by implementing our own option parsing.
The remainder of your concerns relate to the clar implementation itself,
and as such I won't yet address them. I'll work with upstream to get
these fixed after this series lands though, as outlined in [1].
[1]: <ZsSTOczuGhT5zwoV@tanuki>
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v6 03/13] t/clar: fix compatibility with NonStop
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 01/13] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 02/13] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 04/13] clar: avoid compile error with mingw-w64 Patrick Steinhardt
` (10 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f35258..e25057b7c49 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 04/13] clar: avoid compile error with mingw-w64
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (2 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 03/13] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 05/13] clar(win32): avoid compile error due to unused `fs_copy()` Patrick Steinhardt
` (9 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When using mingw-w64 to compile the code, and using `_stat()`, it is
necessary to use `struct _stat`, too, and not `struct stat` (as the
latter is incompatible with the "dashed" version because it is limited
to 32-bit time types for backwards compatibility).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index 3fc2c768158..e2ebe551d38 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -68,7 +68,7 @@
# define PRIxZ "Ix"
# endif
-# if defined(_MSC_VER) || defined(__MINGW32__)
+# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
typedef struct stat STAT_T;
# else
typedef struct _stat STAT_T;
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 05/13] clar(win32): avoid compile error due to unused `fs_copy()`
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (3 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 04/13] clar: avoid compile error with mingw-w64 Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 06/13] clar: stop including `shellapi.h` unnecessarily Patrick Steinhardt
` (8 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When CLAR_FIXTURE_PATH is unset, the `fs_copy()` function seems not to
be used. But it is declared as `static`, and GCC does not like that,
complaining that it should not be declared/defined to begin with.
We could mark this function as (potentially) unused by following the
`MAYBE_UNUSED` pattern from Git's `git-compat-util.h`. However, this is
a GCC-only construct that is not understood by Visual C. Besides, `clar`
does not use that pattern at all.
Instead, let's use the `((void)SYMBOL);` pattern that `clar` already
uses elsewhere; This avoids the compile error by sorta kinda make the
function used after a fashion.
Note: GCC 14.x (which Git for Windows' SDK already uses) is able to
figure out that this function is unused even though there are recursive
calls between `fs_copy()` and `fs_copydir_helper()`; Earlier GCC
versions do not detect that, and therefore the issue has been hidden
from the regular Linux CI builds (where GCC 14.x is not yet used). That
is the reason why this change is only made in the Windows-specific
portion of `t/unit-tests/clar/clar/fs.h`.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/fs.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
index 3e39890bd3e..8b206179fc4 100644
--- a/t/unit-tests/clar/clar/fs.h
+++ b/t/unit-tests/clar/clar/fs.h
@@ -297,6 +297,8 @@ cl_fs_cleanup(void)
{
#ifdef CLAR_FIXTURE_PATH
fs_rm(fixture_path(_clar_path, "*"));
+#else
+ ((void)fs_copy); /* unused */
#endif
}
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 06/13] clar: stop including `shellapi.h` unnecessarily
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (4 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 05/13] clar(win32): avoid compile error due to unused `fs_copy()` Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 07/13] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
` (7 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The `shellapi.h` header was included as of
https://github.com/clar-test/clar/commit/136e763211aa, to have
`SHFileOperation()` declared so that it could be called.
However, https://github.com/clar-test/clar/commit/5ce31b69b525 removed
that call, and therefore that `#include <shellapi.h>` is unnecessary.
It is also unwanted in Git because this project uses a subset of Git for
Windows' SDK in its CI builds that (for bandwidth reasons) excludes tons
of header files, including `shellapi.h`.
So let's remove it.
Note: Since the `windows.h` header would include `shellapi.h` anyway, we
also define `WIN32_LEAN_AND_MEAN` to avoid this and similar other
unnecessary includes before including `windows.h`.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index e2ebe551d38..cef0f023c24 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -19,9 +19,9 @@
#include <sys/stat.h>
#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <io.h>
-# include <shellapi.h>
# include <direct.h>
# define _MAIN_CC __cdecl
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 07/13] Makefile: fix sparse dependency on GENERATED_H
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (5 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 06/13] clar: stop including `shellapi.h` unnecessarily Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 08/13] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
` (6 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The "check" Makefile target is essentially an alias around the "sparse"
target. The one difference though is that it will tell users to instead
run the "test" target in case they do not have sparse(1) installed, as
chances are high that they wanted to execute the test suite rather than
doing semantic checks.
But even though the "check" target ultimately just ends up executing
`make sparse`, it still depends on our generated headers. This does not
make any sense though: they are irrelevant for the "test" target advice,
and if these headers are required for the "sparse" target they must be
declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as
concurrent builds may then end up generating the headers and running
sparse concurrently. Instead, we make them a dependency of the specific
objects. While that is overly broad, it does ensure correct ordering.
The alternative, specifying which file depends on what generated header
explicitly, feels rather unmaintainable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 28742a60964..efd305ab358 100644
--- a/Makefile
+++ b/Makefile
@@ -3254,7 +3254,7 @@ check-sha1:: t/helper/test-tool$X
SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
-$(SP_OBJ): %.sp: %.c %.o
+$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
-Wsparse-error \
$(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3295,7 +3295,7 @@ style:
git clang-format --style file --diff --extensions c,h
.PHONY: check
-check: $(GENERATED_H)
+check:
@if sparse; \
then \
echo >&2 "Use 'make sparse' instead"; \
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 08/13] Makefile: make hdr-check depend on generated headers
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (6 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 07/13] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 09/13] Makefile: do not use sparse on third-party sources Patrick Steinhardt
` (5 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The "hdr-check" Makefile target compiles each of our headers as a
standalone code unit to ensure that they are not missing any type
declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework,
which will have the effect that some headers start depending on
generated ones. While we could declare that dependency explicitly, it
does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects
depend on all of our generated headers. While again overly broad, it is
easy to maintain and generating headers is not an expensive thing to do
anyway.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index efd305ab358..8c4487dd0c6 100644
--- a/Makefile
+++ b/Makefile
@@ -3284,7 +3284,7 @@ HCC = $(HCO:hco=hcc)
@echo '#include "git-compat-util.h"' >$@
@echo '#include "$<"' >>$@
-$(HCO): %.hco: %.hcc FORCE
+$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE
$(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $<
.PHONY: hdr-check $(HCO)
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 09/13] Makefile: do not use sparse on third-party sources
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (7 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 08/13] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 10/13] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (4 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
We have several third-party sources in our codebase that we have
imported from upstream projects. These sources are mostly excluded from
our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 8c4487dd0c6..81a47b61327 100644
--- a/Makefile
+++ b/Makefile
@@ -3252,7 +3252,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
check-sha1:: t/helper/test-tool$X
t/helper/test-sha1.sh
-SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
+SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS)))
+SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC))
$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 10/13] Makefile: wire up the clar unit testing framework
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (8 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 09/13] Makefile: do not use sparse on third-party sources Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-20 14:02 ` [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (3 subsequent siblings)
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 36 +++++++++++++++++++++---
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 18 ++++++++++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 107 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c23..6687bd6db4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 81a47b61327..e38146b5eb0 100644
--- a/Makefile
+++ b/Makefile
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a
GENERATED_H += command-list.h
GENERATED_H += config-list.h
GENERATED_H += hook-list.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar.suite
.PHONY: generated-hdrs
generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3216,7 +3224,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3648,7 +3656,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3704,6 +3712,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3863,7 +3872,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b78..131ffd778fe 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec0..d0632ec7f9e 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 00000000000..ab71ce6c9fc
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 00000000000..32a81299e91
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,18 @@
+#include "unit-test.h"
+
+int cmd_main(int argc, const char **argv)
+{
+ const char **argv_copy;
+ int ret;
+
+ /* Append the "-t" flag such that the tests generate TAP output. */
+ ALLOC_ARRAY(argv_copy, argc + 2);
+ COPY_ARRAY(argv_copy, argv, argc);
+ argv_copy[argc++] = "-t";
+ argv_copy[argc] = NULL;
+
+ ret = clar_test(argc, (char **) argv_copy);
+
+ free(argv_copy);
+ return ret;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 00000000000..66ec2387cc6
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (9 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 10/13] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-28 13:17 ` Phillip Wood
2024-08-20 14:02 ` [PATCH v6 12/13] t/unit-tests: convert ctype " Patrick Steinhardt
` (2 subsequent siblings)
13 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
ok 4 - strvec::push
ok 5 - strvec::pushft_pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/strvec.c | 222 ++++++++++++++++++++++++++++++++++++++++
t/unit-tests/t-strvec.c | 211 --------------------------------------
3 files changed, 223 insertions(+), 212 deletions(-)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
diff --git a/Makefile b/Makefile
index e38146b5eb0..56ce6c00e44 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1356,7 +1357,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-reftable-tree
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/strvec.c b/t/unit-tests/strvec.c
new file mode 100644
index 00000000000..d11ed0f28de
--- /dev/null
+++ b/t/unit-tests/strvec.c
@@ -0,0 +1,222 @@
+#include "unit-test.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define check_strvec(vec, ...) \
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
+ cl_assert(ARRAY_SIZE(expect) > 0); \
+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
+
+void test_strvec__init(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__dynamic_init(void)
+{
+ struct strvec vec;
+ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__clear(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_push(&vec, "foo");
+ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__push(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_push(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+
+ strvec_push(&vec, "bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushf(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushf(&vec, "foo: %d", 1);
+ check_strvec(&vec, "foo: 1", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushl(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushv(void)
+{
+ const char *strings[] = {
+ "foo", "bar", "baz", NULL,
+ };
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushv(&vec, strings);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 0, "replaced");
+ check_strvec(&vec, "replaced", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 2, "replaced");
+ check_strvec(&vec, "foo", "bar", "replaced", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 1, "replaced");
+ check_strvec(&vec, "foo", "replaced", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_with_substring(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", NULL);
+ strvec_replace(&vec, 0, vec.v[0] + 1);
+ check_strvec(&vec, "oo", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 0);
+ check_strvec(&vec, "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 2);
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 1);
+ check_strvec(&vec, "foo", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pop_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pop(&vec);
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pop_non_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_pop(&vec);
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_empty_string(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_split(&vec, "");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_single_item(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_split(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_multiple_items(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_split(&vec, "foo bar baz");
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_whitespace_only(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_split(&vec, " \t\n");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_multiple_consecutive_whitespaces(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_split(&vec, "foo\n\t bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__detach(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ const char **detached;
+
+ strvec_push(&vec, "foo");
+
+ detached = strvec_detach(&vec);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
+
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+
+ free((char *) detached[0]);
+ free(detached);
+}
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
deleted file mode 100644
index c4bac8fc91b..00000000000
--- a/t/unit-tests/t-strvec.c
+++ /dev/null
@@ -1,211 +0,0 @@
-#include "test-lib.h"
-#include "strbuf.h"
-#include "strvec.h"
-
-#define check_strvec(vec, ...) \
- do { \
- const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
- } while (0)
-
-int cmd_main(int argc, const char **argv)
-{
- if_test ("static initialization") {
- struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("dynamic initialization") {
- struct strvec vec;
- strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("clear") {
- struct strvec vec = STRVEC_INIT;
- strvec_push(&vec, "foo");
- strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("push") {
- struct strvec vec = STRVEC_INIT;
-
- strvec_push(&vec, "foo");
- check_strvec(&vec, "foo", NULL);
-
- strvec_push(&vec, "bar");
- check_strvec(&vec, "foo", "bar", NULL);
-
- strvec_clear(&vec);
- }
-
- if_test ("pushf") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushf(&vec, "foo: %d", 1);
- check_strvec(&vec, "foo: 1", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pushl") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- check_strvec(&vec, "foo", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pushv") {
- const char *strings[] = {
- "foo", "bar", "baz", NULL,
- };
- struct strvec vec = STRVEC_INIT;
-
- strvec_pushv(&vec, strings);
- check_strvec(&vec, "foo", "bar", "baz", NULL);
-
- strvec_clear(&vec);
- }
-
- if_test ("replace at head") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 0, "replaced");
- check_strvec(&vec, "replaced", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace at tail") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 2, "replaced");
- check_strvec(&vec, "foo", "bar", "replaced", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace in between") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 1, "replaced");
- check_strvec(&vec, "foo", "replaced", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace with substring") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", NULL);
- strvec_replace(&vec, 0, vec.v[0] + 1);
- check_strvec(&vec, "oo", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove at head") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 0);
- check_strvec(&vec, "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove at tail") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 2);
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove in between") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 1);
- check_strvec(&vec, "foo", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pop with empty array") {
- struct strvec vec = STRVEC_INIT;
- strvec_pop(&vec);
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pop with non-empty array") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_pop(&vec);
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split empty string") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "");
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split single item") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo");
- check_strvec(&vec, "foo", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split multiple items") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo bar baz");
- check_strvec(&vec, "foo", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split whitespace only") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, " \t\n");
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split multiple consecutive whitespaces") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo\n\t bar");
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("detach") {
- struct strvec vec = STRVEC_INIT;
- const char **detached;
-
- strvec_push(&vec, "foo");
-
- detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
-
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
-
- free((char *) detached[0]);
- free(detached);
- }
-
- return test_done();
-}
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
2024-08-20 14:02 ` [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-28 13:17 ` Phillip Wood
2024-09-03 7:45 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-28 13:17 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 20/08/2024 15:02, Patrick Steinhardt wrote:
> Convert the strvec tests to use the new clar unit testing framework.
> This is a first test balloon that demonstrates how the testing infra for
> clar-based tests looks like.
>
> The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
> running that binary, it generates TAP output:
It would be interesting to see a comparison between the current
framework and clar of the output from a failing test - the TAP output
for passing tests is pretty much the same regardless of the framework used.
> # ./t/unit-tests/bin/unit-tests
> TAP version 13
> # start of suite 1: strvec
> ok 1 - strvec::init
> [...]
> The binary also supports some parameters that allow us to run only a
> subset of unit tests or alter the output:
>
> $ ./t/unit-tests/bin/unit-tests -h
> Usage: ./t/unit-tests/bin/unit-tests [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)
The output above seems to include the suite name - are we running the
tests with '-v' from our Makefile?
> -q Only report tests that had an error
This option is incompatible with TAP output. As we force TAP output we
should find a way to stop displaying this help.
> -Q Quit as soon as a test fails
> -t Display results in tap format
We force TAP output by adding '-t' to argv in main() so this line is not
very helpful
> -l Print suite names
> -r[filename] Write summary file (to the optional filename)
> diff --git a/t/unit-tests/strvec.c b/t/unit-tests/strvec.c
> [..]
> +#define check_strvec(vec, ...) \
> + do { \
> + const char *expect[] = { __VA_ARGS__ }; \
> + cl_assert(ARRAY_SIZE(expect) > 0); \
As there are a lot occurrences of ARRAY_SIZE(expect) it is probably
worth adding
size_t expect_len = ARRAY_SIZE(expect);
above.
> + cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
> + cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
> + cl_assert((vec)->nr <= (vec)->alloc); \
The conversion here loses the values of nr and alloc which is a shame as
they would be useful when debugging a test failure.
> + for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
> + cl_assert_equal_s((vec)->v[i], expect[i]); \
The original test also printed the array index of the failing check. As
the elements of the test vectors all seem to be unique that is less of a
worry than if we had tests with repeating elements.
> + } while (0)
> +
> +void test_strvec__init(void)
> +{
> + struct strvec vec = STRVEC_INIT;
If we're rewriting the tests perhaps we can take the opportunity to add
a blank line to each one after the variable declarations in accordance
with our coding guidelines.
It might be a good opportunity to show the set-up and tear-down
facilities in clar as well instead of repeating the initialization in
each test.
Best Wishes
Phillip
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> +}
> +
> +void test_strvec__dynamic_init(void)
> +{
> + struct strvec vec;
> + strvec_init(&vec);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> +}
> +
> +void test_strvec__clear(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_push(&vec, "foo");
> + strvec_clear(&vec);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> +}
> +
> +void test_strvec__push(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> +
> + strvec_push(&vec, "foo");
> + check_strvec(&vec, "foo", NULL);
> +
> + strvec_push(&vec, "bar");
> + check_strvec(&vec, "foo", "bar", NULL);
> +
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__pushf(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushf(&vec, "foo: %d", 1);
> + check_strvec(&vec, "foo: 1", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__pushl(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + check_strvec(&vec, "foo", "bar", "baz", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__pushv(void)
> +{
> + const char *strings[] = {
> + "foo", "bar", "baz", NULL,
> + };
> + struct strvec vec = STRVEC_INIT;
> +
> + strvec_pushv(&vec, strings);
> + check_strvec(&vec, "foo", "bar", "baz", NULL);
> +
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__replace_at_head(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_replace(&vec, 0, "replaced");
> + check_strvec(&vec, "replaced", "bar", "baz", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__replace_at_tail(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_replace(&vec, 2, "replaced");
> + check_strvec(&vec, "foo", "bar", "replaced", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__replace_in_between(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_replace(&vec, 1, "replaced");
> + check_strvec(&vec, "foo", "replaced", "baz", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__replace_with_substring(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", NULL);
> + strvec_replace(&vec, 0, vec.v[0] + 1);
> + check_strvec(&vec, "oo", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__remove_at_head(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_remove(&vec, 0);
> + check_strvec(&vec, "bar", "baz", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__remove_at_tail(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_remove(&vec, 2);
> + check_strvec(&vec, "foo", "bar", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__remove_in_between(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_remove(&vec, 1);
> + check_strvec(&vec, "foo", "baz", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__pop_empty_array(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pop(&vec);
> + check_strvec(&vec, NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__pop_non_empty_array(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_pop(&vec);
> + check_strvec(&vec, "foo", "bar", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__split_empty_string(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_split(&vec, "");
> + check_strvec(&vec, NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__split_single_item(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_split(&vec, "foo");
> + check_strvec(&vec, "foo", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__split_multiple_items(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_split(&vec, "foo bar baz");
> + check_strvec(&vec, "foo", "bar", "baz", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__split_whitespace_only(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_split(&vec, " \t\n");
> + check_strvec(&vec, NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__split_multiple_consecutive_whitespaces(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + strvec_split(&vec, "foo\n\t bar");
> + check_strvec(&vec, "foo", "bar", NULL);
> + strvec_clear(&vec);
> +}
> +
> +void test_strvec__detach(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> + const char **detached;
> +
> + strvec_push(&vec, "foo");
> +
> + detached = strvec_detach(&vec);
> + cl_assert_equal_s(detached[0], "foo");
> + cl_assert_equal_p(detached[1], NULL);
> +
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> +
> + free((char *) detached[0]);
> + free(detached);
> +}
> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
> deleted file mode 100644
> index c4bac8fc91b..00000000000
> --- a/t/unit-tests/t-strvec.c
> +++ /dev/null
> @@ -1,211 +0,0 @@
> -#include "test-lib.h"
> -#include "strbuf.h"
> -#include "strvec.h"
> -
> -#define check_strvec(vec, ...) \
> - do { \
> - const char *expect[] = { __VA_ARGS__ }; \
> - if (check_uint(ARRAY_SIZE(expect), >, 0) && \
> - check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
> - check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
> - check_uint((vec)->nr, <=, (vec)->alloc)) { \
> - for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
> - if (!check_str((vec)->v[i], expect[i])) { \
> - test_msg(" i: %"PRIuMAX, \
> - (uintmax_t)i); \
> - break; \
> - } \
> - } \
> - } \
> - } while (0)
> -
> -int cmd_main(int argc, const char **argv)
> -{
> - if_test ("static initialization") {
> - struct strvec vec = STRVEC_INIT;
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> - }
> -
> - if_test ("dynamic initialization") {
> - struct strvec vec;
> - strvec_init(&vec);
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> - }
> -
> - if_test ("clear") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_push(&vec, "foo");
> - strvec_clear(&vec);
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> - }
> -
> - if_test ("push") {
> - struct strvec vec = STRVEC_INIT;
> -
> - strvec_push(&vec, "foo");
> - check_strvec(&vec, "foo", NULL);
> -
> - strvec_push(&vec, "bar");
> - check_strvec(&vec, "foo", "bar", NULL);
> -
> - strvec_clear(&vec);
> - }
> -
> - if_test ("pushf") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushf(&vec, "foo: %d", 1);
> - check_strvec(&vec, "foo: 1", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("pushl") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - check_strvec(&vec, "foo", "bar", "baz", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("pushv") {
> - const char *strings[] = {
> - "foo", "bar", "baz", NULL,
> - };
> - struct strvec vec = STRVEC_INIT;
> -
> - strvec_pushv(&vec, strings);
> - check_strvec(&vec, "foo", "bar", "baz", NULL);
> -
> - strvec_clear(&vec);
> - }
> -
> - if_test ("replace at head") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_replace(&vec, 0, "replaced");
> - check_strvec(&vec, "replaced", "bar", "baz", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("replace at tail") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_replace(&vec, 2, "replaced");
> - check_strvec(&vec, "foo", "bar", "replaced", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("replace in between") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_replace(&vec, 1, "replaced");
> - check_strvec(&vec, "foo", "replaced", "baz", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("replace with substring") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", NULL);
> - strvec_replace(&vec, 0, vec.v[0] + 1);
> - check_strvec(&vec, "oo", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("remove at head") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_remove(&vec, 0);
> - check_strvec(&vec, "bar", "baz", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("remove at tail") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_remove(&vec, 2);
> - check_strvec(&vec, "foo", "bar", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("remove in between") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_remove(&vec, 1);
> - check_strvec(&vec, "foo", "baz", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("pop with empty array") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pop(&vec);
> - check_strvec(&vec, NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("pop with non-empty array") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> - strvec_pop(&vec);
> - check_strvec(&vec, "foo", "bar", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("split empty string") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "");
> - check_strvec(&vec, NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("split single item") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "foo");
> - check_strvec(&vec, "foo", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("split multiple items") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "foo bar baz");
> - check_strvec(&vec, "foo", "bar", "baz", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("split whitespace only") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, " \t\n");
> - check_strvec(&vec, NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("split multiple consecutive whitespaces") {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "foo\n\t bar");
> - check_strvec(&vec, "foo", "bar", NULL);
> - strvec_clear(&vec);
> - }
> -
> - if_test ("detach") {
> - struct strvec vec = STRVEC_INIT;
> - const char **detached;
> -
> - strvec_push(&vec, "foo");
> -
> - detached = strvec_detach(&vec);
> - check_str(detached[0], "foo");
> - check_pointer_eq(detached[1], NULL);
> -
> - check_pointer_eq(vec.v, empty_strvec);
> - check_uint(vec.nr, ==, 0);
> - check_uint(vec.alloc, ==, 0);
> -
> - free((char *) detached[0]);
> - free(detached);
> - }
> -
> - return test_done();
> -}
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
2024-08-28 13:17 ` Phillip Wood
@ 2024-09-03 7:45 ` Patrick Steinhardt
2024-09-03 9:48 ` phillip.wood123
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 7:45 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Wed, Aug 28, 2024 at 02:17:05PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 20/08/2024 15:02, Patrick Steinhardt wrote:
> > Convert the strvec tests to use the new clar unit testing framework.
> > This is a first test balloon that demonstrates how the testing infra for
> > clar-based tests looks like.
> >
> > The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
> > running that binary, it generates TAP output:
>
> It would be interesting to see a comparison between the current framework
> and clar of the output from a failing test - the TAP output for passing
> tests is pretty much the same regardless of the framework used.
Will do.
> > # ./t/unit-tests/bin/unit-tests
> > TAP version 13
> > # start of suite 1: strvec
> > ok 1 - strvec::init
> > [...] The binary also supports some parameters that allow us to run only
> > a
> > subset of unit tests or alter the output:
> >
> > $ ./t/unit-tests/bin/unit-tests -h
> > Usage: ./t/unit-tests/bin/unit-tests [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)
>
> The output above seems to include the suite name - are we running the tests
> with '-v' from our Makefile?
The `-v` switch is actually doing nothing when generating TAP output.
> > -q Only report tests that had an error
>
> This option is incompatible with TAP output. As we force TAP output we
> should find a way to stop displaying this help.
>
> > -Q Quit as soon as a test fails
> > -t Display results in tap format
>
> We force TAP output by adding '-t' to argv in main() so this line is not
> very helpful
True indeed. This is the default argument parsing and output from clar,
so it's nothing that we can change. That being said, I guess the best
way to address this is to use our own option parsing here instead of
using whatever clar provides, and then we can also print our own usage.
Will amend accordingly.
> > -l Print suite names
> > -r[filename] Write summary file (to the optional filename)
>
> > diff --git a/t/unit-tests/strvec.c b/t/unit-tests/strvec.c
> > [..]
> > +#define check_strvec(vec, ...) \
> > + do { \
> > + const char *expect[] = { __VA_ARGS__ }; \
> > + cl_assert(ARRAY_SIZE(expect) > 0); \
>
> As there are a lot occurrences of ARRAY_SIZE(expect) it is probably worth
> adding
>
> size_t expect_len = ARRAY_SIZE(expect);
>
> above.
Can do.
> > + cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
> > + cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
> > + cl_assert((vec)->nr <= (vec)->alloc); \
>
> The conversion here loses the values of nr and alloc which is a shame as
> they would be useful when debugging a test failure.
This is something I'd address in the future, once we have macros that
can do relative comparisons.
> > + for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
> > + cl_assert_equal_s((vec)->v[i], expect[i]); \
>
> The original test also printed the array index of the failing check. As the
> elements of the test vectors all seem to be unique that is less of a worry
> than if we had tests with repeating elements.
>
> > + } while (0)
> > +
> > +void test_strvec__init(void)
> > +{
> > + struct strvec vec = STRVEC_INIT;
>
> If we're rewriting the tests perhaps we can take the opportunity to add a
> blank line to each one after the variable declarations in accordance with
> our coding guidelines.
Can do.
> It might be a good opportunity to show the set-up and tear-down facilities
> in clar as well instead of repeating the initialization in each test.
I don't think it's a good fit here, as setup and teardown would hit the
system under test. I rather think they should be used in cases where you
e.g. always have to setup a repository for your tests.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
2024-09-03 7:45 ` Patrick Steinhardt
@ 2024-09-03 9:48 ` phillip.wood123
2024-09-04 6:37 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: phillip.wood123 @ 2024-09-03 9:48 UTC (permalink / raw)
To: Patrick Steinhardt, phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 03/09/2024 08:45, Patrick Steinhardt wrote:
> On Wed, Aug 28, 2024 at 02:17:05PM +0100, Phillip Wood wrote:
>> Hi Patrick
>>
>> On 20/08/2024 15:02, Patrick Steinhardt wrote:
>> It might be a good opportunity to show the set-up and tear-down facilities
>> in clar as well instead of repeating the initialization in each test.
>
> I don't think it's a good fit here, as setup and teardown would hit the
> system under test. I rather think they should be used in cases where you
> e.g. always have to setup a repository for your tests.
I'm not sure I follow. I was suggesting we define
test_strvec__initialize() to initialize a global strvec which the tests
use and is then freed by test_strvec__cleanup() like the tests/adding.c
example the clar's README.md. That would allow use to remove the setup
and teardown from each test. As I understand it clar's setup/cleanup
functionality is usable without setting up a sandbox directory for each
test.
I'll take a look at v7 in the next few days - I suspect we're getting to
the point where it's ready to be merged.
Best Wishes
Phillip
> Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
2024-09-03 9:48 ` phillip.wood123
@ 2024-09-04 6:37 ` Patrick Steinhardt
2024-09-04 9:31 ` phillip.wood123
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 6:37 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Tue, Sep 03, 2024 at 10:48:01AM +0100, phillip.wood123@gmail.com wrote:
> Hi Patrick
>
> On 03/09/2024 08:45, Patrick Steinhardt wrote:
> > On Wed, Aug 28, 2024 at 02:17:05PM +0100, Phillip Wood wrote:
> > > Hi Patrick
> > >
> > > On 20/08/2024 15:02, Patrick Steinhardt wrote:
> > > It might be a good opportunity to show the set-up and tear-down facilities
> > > in clar as well instead of repeating the initialization in each test.
> >
> > I don't think it's a good fit here, as setup and teardown would hit the
> > system under test. I rather think they should be used in cases where you
> > e.g. always have to setup a repository for your tests.
>
> I'm not sure I follow. I was suggesting we define test_strvec__initialize()
> to initialize a global strvec which the tests use and is then freed by
> test_strvec__cleanup() like the tests/adding.c example the clar's README.md.
> That would allow use to remove the setup and teardown from each test. As I
> understand it clar's setup/cleanup functionality is usable without setting
> up a sandbox directory for each test.
What I'm saying is that `strvec_init()` itself is part of the system
under test, so evicting that into a `__initialize()` function doesn't
quite make sense to me. If there was for example a bug somewhere in the
strvec code we might bring the global `struct strvec` into a state that
is completely unusable, and thus all subsequent tests would fail. We
could of course work around that by always zeroing out the struct, but
because of that I just don't think it's a good fit.
I rather see the usefulness of `__initialize()` in setting up auxiliary
data structures that are a dependency of the system under test, but
which are not the system under test itself.
> I'll take a look at v7 in the next few days - I suspect we're getting to the
> point where it's ready to be merged.
Thanks!
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar
2024-09-04 6:37 ` Patrick Steinhardt
@ 2024-09-04 9:31 ` phillip.wood123
0 siblings, 0 replies; 172+ messages in thread
From: phillip.wood123 @ 2024-09-04 9:31 UTC (permalink / raw)
To: Patrick Steinhardt, phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On 04/09/2024 07:37, Patrick Steinhardt wrote:
> On Tue, Sep 03, 2024 at 10:48:01AM +0100, phillip.wood123@gmail.com wrote:
>> On 03/09/2024 08:45, Patrick Steinhardt wrote:
>>> On Wed, Aug 28, 2024 at 02:17:05PM +0100, Phillip Wood wrote:
>>>> On 20/08/2024 15:02, Patrick Steinhardt wrote:
>>>> It might be a good opportunity to show the set-up and tear-down facilities
>>>> in clar as well instead of repeating the initialization in each test.
>>>
>>> I don't think it's a good fit here, as setup and teardown would hit the
>>> system under test. I rather think they should be used in cases where you
>>> e.g. always have to setup a repository for your tests.
>>
>> I'm not sure I follow. I was suggesting we define test_strvec__initialize()
>> to initialize a global strvec which the tests use and is then freed by
>> test_strvec__cleanup() like the tests/adding.c example the clar's README.md.
>> That would allow use to remove the setup and teardown from each test. As I
>> understand it clar's setup/cleanup functionality is usable without setting
>> up a sandbox directory for each test.
>
> What I'm saying is that `strvec_init()` itself is part of the system
> under test,
Oh, I see - I'd misunderstood what you meant by "system under test"
> so evicting that into a `__initialize()` function doesn't
> quite make sense to me. If there was for example a bug somewhere in the
> strvec code we might bring the global `struct strvec` into a state that
> is completely unusable, and thus all subsequent tests would fail. We
> could of course work around that by always zeroing out the struct, but
> because of that I just don't think it's a good fit.
Isn't the point of strvec_init() to ensure that the `struct strvec`
passed to it is usable? If it does not do that then having all the tests
fail is good as it tells us there is a bug. I was hoping that
__initialize() would be a useful replacement for the
TEST(setup(test_fn()), "description")
pattern in some of our existing tests. I find hoisting the common
initialization and cleanup out of each tests makes them easier to review
as I can review the setup code once and not worry about it when
reviewing each test. It also avoids a lot of repetition when writing
tests and keeps the test bodies concise.
> I rather see the usefulness of `__initialize()` in setting up auxiliary
> data structures that are a dependency of the system under test, but
> which are not the system under test itself.
That's certainly a good use for them
Best Wishes
Phillip
>> I'll take a look at v7 in the next few days - I suspect we're getting to the
>> point where it's ready to be merged.
>
> Thanks!
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v6 12/13] t/unit-tests: convert ctype tests to use clar
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (10 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 11/13] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-28 13:18 ` Phillip Wood
2024-08-20 14:02 ` [PATCH v6 13/13] clar: add CMake support Patrick Steinhardt
2024-08-28 13:18 ` [PATCH v6 00/13] Introduce clar testing framework Phillip Wood
13 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Convert the ctype tests to use the new clar unit testing framework.
Introduce a new function `cl_failf()` that allows us to print a
formatted error message, which we can use to point out which of the
characters was classified incorrectly. This results in output like this
on failure:
# start of suite 1: ctype
ok 1 - ctype::isspace
not ok 2 - ctype::isdigit
---
reason: |
Test failed.
0x61 is classified incorrectly
at:
file: 't/unit-tests/ctype.c'
line: 38
function: 'test_ctype__isdigit'
---
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
ok 6 - ctype::is_regex_special
ok 7 - ctype::is_pathspec_magic
ok 8 - ctype::isascii
ok 9 - ctype::islower
ok 10 - ctype::isupper
ok 11 - ctype::iscntrl
ok 12 - ctype::ispunct
ok 13 - ctype::isxdigit
ok 14 - ctype::isprint
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 70 ++++++++++++++++++++++++-----
t/unit-tests/unit-test.h | 7 +++
3 files changed, 67 insertions(+), 12 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (70%)
diff --git a/Makefile b/Makefile
index 56ce6c00e44..c841cf70063 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-hashmap
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 70%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index e28a7f50f9a..5026378bfbc 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,16 +1,13 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- if_test (#class " works") { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
- } \
+ for (int i = 0; i < 256; i++) \
+ if (class(i) != !!memchr(string, i, len)) \
+ cl_failf("0x%02x is classified incorrectly", i); \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -31,21 +28,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
index 66ec2387cc6..4c461defe16 100644
--- a/t/unit-tests/unit-test.h
+++ b/t/unit-tests/unit-test.h
@@ -1,3 +1,10 @@
#include "git-compat-util.h"
#include "clar/clar.h"
#include "clar-decls.h"
+#include "strbuf.h"
+
+#define cl_failf(fmt, ...) do { \
+ char *desc = xstrfmt(fmt, __VA_ARGS__); \
+ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
+ free(desc); \
+} while (0)
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v6 12/13] t/unit-tests: convert ctype tests to use clar
2024-08-20 14:02 ` [PATCH v6 12/13] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-08-28 13:18 ` Phillip Wood
2024-09-03 7:45 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-28 13:18 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 20/08/2024 15:02, Patrick Steinhardt wrote:
> Convert the ctype tests to use the new clar unit testing framework.
> Introduce a new function `cl_failf()`
This is a nice addition which somewhat mitigates the lack of an
equivalent to test_msg() that adds addition context messages to test
failures.
> that allows us to print a
> formatted error message, which we can use to point out which of the
> characters was classified incorrectly. This results in output like this
> on failure:
>
> # start of suite 1: ctype
> ok 1 - ctype::isspace
> not ok 2 - ctype::isdigit
> ---
> reason: |
> Test failed.
"Test failed." is not the reason for the test failure
> 0x61 is classified incorrectly
> at:
> file: 't/unit-tests/ctype.c'
> line: 38
> function: 'test_ctype__isdigit'
> ---
This is rather verbose compared to the current framework
# check "isdigit(i) == !!memchr("123456789", i, len)" failed at
t/unit-tests/t-ctype.c:36
# left: 1
# right: 0
# i: 0x30
not ok 2 - isdigit works
The current tests also shows which characters are expected to return
true and distinguishes between the two possible failure modes which are
(a) misclassification and (b) returning a non-zero integer other than
'1' as "true". The new test output does not allow the person running the
test to distinguish between these two failure modes.
> diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
> index 66ec2387cc6..4c461defe16 100644
> --- a/t/unit-tests/unit-test.h
> +++ b/t/unit-tests/unit-test.h
> @@ -1,3 +1,10 @@
> #include "git-compat-util.h"
> #include "clar/clar.h"
> #include "clar-decls.h"
> +#include "strbuf.h"
> +
> +#define cl_failf(fmt, ...) do { \
> + char *desc = xstrfmt(fmt, __VA_ARGS__); \
In our current framework we avoid relying on the strbuf api and
functions like xstrfmt() that use it as we want to be able to test
strbuf.c with the framework. We'd be better with a macro wrapping a new
function that uses a stack allocated buffer and p_snprintf() like
clar__assert_equal(). That would allow us to upstream this change as well.
> + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
> + free(desc); \
This is leaked on failure due to the use of longjmp.
Best Wishes
Phillip
> +} while (0)
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 12/13] t/unit-tests: convert ctype tests to use clar
2024-08-28 13:18 ` Phillip Wood
@ 2024-09-03 7:45 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 7:45 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Wed, Aug 28, 2024 at 02:18:02PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 20/08/2024 15:02, Patrick Steinhardt wrote:
> > Convert the ctype tests to use the new clar unit testing framework.
> > Introduce a new function `cl_failf()`
>
> This is a nice addition which somewhat mitigates the lack of an equivalent
> to test_msg() that adds addition context messages to test failures.
>
> > that allows us to print a
> > formatted error message, which we can use to point out which of the
> > characters was classified incorrectly. This results in output like this
> > on failure:
> >
> > # start of suite 1: ctype
> > ok 1 - ctype::isspace
> > not ok 2 - ctype::isdigit
> > ---
> > reason: |
> > Test failed.
>
> "Test failed." is not the reason for the test failure
The clar expects two strings, one "general" description and the details.
I agree that it's a bit shoddy to have this as part of the reason, it
really should be a separate "field". But it allows us to distinguish
e.g. between test failures and tests which emitted a warning, only.
> > 0x61 is classified incorrectly
> > at:
> > file: 't/unit-tests/ctype.c'
> > line: 38
> > function: 'test_ctype__isdigit'
> > ---
>
> This is rather verbose compared to the current framework
I guess this is a matter of taste. I actually prefer the more verbose
style.
> # check "isdigit(i) == !!memchr("123456789", i, len)" failed at
> t/unit-tests/t-ctype.c:36
> # left: 1
> # right: 0
> # i: 0x30
> not ok 2 - isdigit works
>
> The current tests also shows which characters are expected to return true
> and distinguishes between the two possible failure modes which are (a)
> misclassification and (b) returning a non-zero integer other than '1' as
> "true". The new test output does not allow the person running the test to
> distinguish between these two failure modes.
This one is true though. Will amend.
> > diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
> > index 66ec2387cc6..4c461defe16 100644
> > --- a/t/unit-tests/unit-test.h
> > +++ b/t/unit-tests/unit-test.h
> > @@ -1,3 +1,10 @@
> > #include "git-compat-util.h"
> > #include "clar/clar.h"
> > #include "clar-decls.h"
> > +#include "strbuf.h"
> > +
> > +#define cl_failf(fmt, ...) do { \
> > + char *desc = xstrfmt(fmt, __VA_ARGS__); \
>
> In our current framework we avoid relying on the strbuf api and functions
> like xstrfmt() that use it as we want to be able to test strbuf.c with the
> framework. We'd be better with a macro wrapping a new function that uses a
> stack allocated buffer and p_snprintf() like clar__assert_equal(). That
> would allow us to upstream this change as well.
I don't think it's all that important to avoid a low-level API like
`xstrfmt()`. But the second argument makes sense to me indeed.
> > + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
> > + free(desc); \
>
> This is leaked on failure due to the use of longjmp.
As discussed elsewhere I don't think this leak matters all that much.
But it's getting fixed with your proposal, so that's that.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v6 13/13] clar: add CMake support
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (11 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 12/13] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-08-20 14:02 ` Patrick Steinhardt
2024-08-28 13:18 ` [PATCH v6 00/13] Introduce clar testing framework Phillip Wood
13 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-20 14:02 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Now that we're using `clar` as powerful test framework, we have to
adjust the Visual C build (read: the CMake definition) to be able to
handle that, too.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 832f46b316b..608fd3fe709 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -1004,6 +1004,59 @@ foreach(unit_test ${unit_test_PROGRAMS})
endif()
endforeach()
+parse_makefile_for_scripts(unit_tests_SUITES "UNIT_TESTS_SUITES" "")
+
+set(clar_decls "")
+set(clar_cbs "")
+set(clar_cbs_count 0)
+set(clar_suites "static struct clar_suite _clar_suites[] = {\n")
+list(LENGTH unit_tests_SUITES clar_suites_count)
+foreach(suite ${unit_tests_SUITES})
+ file(STRINGS "${CMAKE_SOURCE_DIR}/t/unit-tests/${suite}.c" decls
+ REGEX "^void test_${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*\\(void\\)$")
+
+ list(LENGTH decls decls_count)
+ string(REGEX REPLACE "void (test_${suite}__([a-zA-Z_0-9]*))\\(void\\)" " { \"\\2\", &\\1 },\n" cbs ${decls})
+ string(JOIN "" cbs ${cbs})
+ list(TRANSFORM decls PREPEND "extern ")
+ string(JOIN ";\n" decls ${decls})
+
+ string(APPEND clar_decls "${decls};\n")
+ string(APPEND clar_cbs
+ "static const struct clar_func _clar_cb_${suite}[] = {\n"
+ ${cbs}
+ "};\n")
+ string(APPEND clar_suites
+ " {\n"
+ " \"${suite}\",\n"
+ " { NULL, NULL },\n"
+ " { NULL, NULL },\n"
+ " _clar_cb_${suite}, ${decls_count}, 1\n"
+ " },\n")
+ math(EXPR clar_cbs_count "${clar_cbs_count}+${decls_count}")
+endforeach()
+string(APPEND clar_suites
+ "};\n"
+ "static const size_t _clar_suite_count = ${clar_suites_count};\n"
+ "static const size_t _clar_callback_count = ${clar_cbs_count};\n")
+file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h" "${clar_decls}")
+file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite" "${clar_decls}" "${clar_cbs}" "${clar_suites}")
+
+list(TRANSFORM unit_tests_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/")
+list(TRANSFORM unit_tests_SUITES APPEND ".c")
+add_library(unit-tests-lib ${unit_tests_SUITES} "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c")
+target_include_directories(unit-tests-lib PRIVATE "${CMAKE_SOURCE_DIR}/t/unit-tests")
+add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c")
+target_link_libraries(unit-tests unit-tests-lib common-main)
+set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+if(MSVC)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+endif()
+
#test-tool
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
--
2.46.0.164.g477ce5ccd6.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v6 00/13] Introduce clar testing framework
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
` (12 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 13/13] clar: add CMake support Patrick Steinhardt
@ 2024-08-28 13:18 ` Phillip Wood
2024-08-28 14:03 ` Patrick Steinhardt
13 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-08-28 13:18 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On 20/08/2024 15:02, Patrick Steinhardt wrote:
> Hi,
>
> this is another version of my patch series that introduces the clar
> testing framework for our C unit tests.
I've left some comments on the code, most of them are points I made in
the last round that received no response. My main concern is that the
assertions offered by clar are not as convinent as the check_* macros.
What's the plan for converting our current tests if this gets merged? If
we were to add wrappers that provide check_int() etc. that would greatly
simplify the conversion. I think it would offer a more ergonomic api for
writing new tests than the verbose and non-typesafe cl_assert_equal_i()
and friends.
Best Wishes
Phillip
> Changes compared to v5:
>
> - Rebased on top of `master` to fix some merge conflicts. This series
> is now built on bb9c16bd4f (The sixth batch, 2024-08-19).
>
> - Introduce `cl_failf()` as a nice way to print formatted error
> messages.
>
> - NULL-terminate the argv array again and move the changes into the
> correct patch.
>
> - Most of the changes were done by Dscho to port clar over to Windows,
> including CMake build instructions to make this usable with MSVC. I
> plan to upstream these changes when this series lands in Git.
>
> - Introduce CMake support, also by Dscho.
>
> Thanks for all the feedback, and thanks to Dscho for helping out with
> porting this to Windows.
>
> GitHub pipeline: https://github.com/git/git/actions/runs/10472008177/
> GitLab pipeline: https://gitlab.com/gitlab-org/git/-/pipelines/1419946971
>
> Patrick
>
> Johannes Schindelin (4):
> clar: avoid compile error with mingw-w64
> clar(win32): avoid compile error due to unused `fs_copy()`
> clar: stop including `shellapi.h` unnecessarily
> clar: add CMake support
>
> Patrick Steinhardt (9):
> t: do not pass GIT_TEST_OPTS to unit tests with prove
> t: import the clar unit testing framework
> t/clar: fix compatibility with NonStop
> Makefile: fix sparse dependency on GENERATED_H
> Makefile: make hdr-check depend on generated headers
> Makefile: do not use sparse on third-party sources
> Makefile: wire up the clar unit testing framework
> t/unit-tests: convert strvec tests to use clar
> t/unit-tests: convert ctype tests to use clar
>
> .gitignore | 1 +
> Documentation/technical/unit-tests.txt | 2 +
> Makefile | 53 +-
> contrib/buildsystems/CMakeLists.txt | 53 ++
> t/Makefile | 4 +-
> t/run-test.sh | 2 +-
> t/unit-tests/.gitignore | 2 +
> t/unit-tests/clar-generate.awk | 50 ++
> t/unit-tests/clar/.github/workflows/ci.yml | 23 +
> t/unit-tests/clar/COPYING | 15 +
> t/unit-tests/clar/README.md | 329 ++++++++
> t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
> t/unit-tests/clar/clar.h | 173 +++++
> t/unit-tests/clar/clar/fixtures.h | 50 ++
> t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
> t/unit-tests/clar/clar/print.h | 211 ++++++
> t/unit-tests/clar/clar/sandbox.h | 159 ++++
> t/unit-tests/clar/clar/summary.h | 143 ++++
> t/unit-tests/clar/generate.py | 266 +++++++
> t/unit-tests/clar/test/.gitignore | 4 +
> t/unit-tests/clar/test/Makefile | 39 +
> t/unit-tests/clar/test/clar_test.h | 16 +
> t/unit-tests/clar/test/main.c | 40 +
> t/unit-tests/clar/test/main.c.sample | 27 +
> t/unit-tests/clar/test/resources/test/file | 1 +
> t/unit-tests/clar/test/sample.c | 84 ++
> t/unit-tests/{t-ctype.c => ctype.c} | 70 +-
> t/unit-tests/strvec.c | 222 ++++++
> t/unit-tests/t-strvec.c | 211 ------
> t/unit-tests/unit-test.c | 18 +
> t/unit-tests/unit-test.h | 10 +
> 31 files changed, 3409 insertions(+), 235 deletions(-)
> create mode 100644 t/unit-tests/clar-generate.awk
> create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
> create mode 100644 t/unit-tests/clar/COPYING
> create mode 100644 t/unit-tests/clar/README.md
> create mode 100644 t/unit-tests/clar/clar.c
> create mode 100644 t/unit-tests/clar/clar.h
> create mode 100644 t/unit-tests/clar/clar/fixtures.h
> create mode 100644 t/unit-tests/clar/clar/fs.h
> create mode 100644 t/unit-tests/clar/clar/print.h
> create mode 100644 t/unit-tests/clar/clar/sandbox.h
> create mode 100644 t/unit-tests/clar/clar/summary.h
> create mode 100755 t/unit-tests/clar/generate.py
> create mode 100644 t/unit-tests/clar/test/.gitignore
> create mode 100644 t/unit-tests/clar/test/Makefile
> create mode 100644 t/unit-tests/clar/test/clar_test.h
> create mode 100644 t/unit-tests/clar/test/main.c
> create mode 100644 t/unit-tests/clar/test/main.c.sample
> create mode 100644 t/unit-tests/clar/test/resources/test/file
> create mode 100644 t/unit-tests/clar/test/sample.c
> rename t/unit-tests/{t-ctype.c => ctype.c} (70%)
> create mode 100644 t/unit-tests/strvec.c
> delete mode 100644 t/unit-tests/t-strvec.c
> create mode 100644 t/unit-tests/unit-test.c
> create mode 100644 t/unit-tests/unit-test.h
>
> Range-diff against v5:
> 1: 832dc0496fb = 1: e48a6461137 t: do not pass GIT_TEST_OPTS to unit tests with prove
> 2: 36906079330 = 2: 1710e9f9ff7 t: import the clar unit testing framework
> 3: db53673294e = 3: 5c21aa87aa2 t/clar: fix compatibility with NonStop
> -: ----------- > 4: 06d2bce0d82 clar: avoid compile error with mingw-w64
> -: ----------- > 5: f88b3421a09 clar(win32): avoid compile error due to unused `fs_copy()`
> -: ----------- > 6: 5fb4c55be33 clar: stop including `shellapi.h` unnecessarily
> 4: b6199c88dd7 = 7: e0dcbd5ca83 Makefile: fix sparse dependency on GENERATED_H
> 5: 06364b2b722 = 8: 77a03f8df70 Makefile: make hdr-check depend on generated headers
> 6: 88ea94ce16c = 9: c91dd7327e3 Makefile: do not use sparse on third-party sources
> 7: 05bcb5bef6c = 10: 115c15aa9ae Makefile: wire up the clar unit testing framework
> 8: 8f56b4d6264 ! 11: b3b8df04872 t/unit-tests: convert strvec tests to use clar
> @@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
> UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
> UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
> UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
> -@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
> - UNIT_TEST_PROGRAMS += t-reftable-record
> +@@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-record
> + UNIT_TEST_PROGRAMS += t-reftable-tree
> UNIT_TEST_PROGRAMS += t-strbuf
> UNIT_TEST_PROGRAMS += t-strcmp-offset
> -UNIT_TEST_PROGRAMS += t-strvec
> @@ Makefile: UNIT_TEST_PROGRAMS += t-reftable-merged
> UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
> UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
>
> - ## t/unit-tests/t-strvec.c => t/unit-tests/strvec.c ##
> + ## t/unit-tests/strvec.c (new) ##
> @@
> --#include "test-lib.h"
> +#include "unit-test.h"
> - #include "strbuf.h"
> - #include "strvec.h"
> -
> - #define check_strvec(vec, ...) \
> - do { \
> - const char *expect[] = { __VA_ARGS__ }; \
> -- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
> -- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
> -- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
> -- check_uint((vec)->nr, <=, (vec)->alloc)) { \
> -- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
> -- if (!check_str((vec)->v[i], expect[i])) { \
> -- test_msg(" i: %"PRIuMAX, \
> -- (uintmax_t)i); \
> -- break; \
> -- } \
> -- } \
> -- } \
> ++#include "strbuf.h"
> ++#include "strvec.h"
> ++
> ++#define check_strvec(vec, ...) \
> ++ do { \
> ++ const char *expect[] = { __VA_ARGS__ }; \
> + cl_assert(ARRAY_SIZE(expect) > 0); \
> + cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
> + cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
> + cl_assert((vec)->nr <= (vec)->alloc); \
> + for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
> + cl_assert_equal_s((vec)->v[i], expect[i]); \
> - } while (0)
> -
> --static void t_static_init(void)
> ++ } while (0)
> ++
> +void test_strvec__init(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> -- check_pointer_eq(vec.v, empty_strvec);
> -- check_uint(vec.nr, ==, 0);
> -- check_uint(vec.alloc, ==, 0);
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> - }
> -
> --static void t_dynamic_init(void)
> ++}
> ++
> +void test_strvec__dynamic_init(void)
> - {
> - struct strvec vec;
> - strvec_init(&vec);
> -- check_pointer_eq(vec.v, empty_strvec);
> -- check_uint(vec.nr, ==, 0);
> -- check_uint(vec.alloc, ==, 0);
> ++{
> ++ struct strvec vec;
> ++ strvec_init(&vec);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> - }
> -
> --static void t_clear(void)
> ++}
> ++
> +void test_strvec__clear(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_push(&vec, "foo");
> - strvec_clear(&vec);
> -- check_pointer_eq(vec.v, empty_strvec);
> -- check_uint(vec.nr, ==, 0);
> -- check_uint(vec.alloc, ==, 0);
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_push(&vec, "foo");
> ++ strvec_clear(&vec);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> - }
> -
> --static void t_push(void)
> ++}
> ++
> +void test_strvec__push(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> -
> -@@ t/unit-tests/strvec.c: static void t_push(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_pushf(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++
> ++ strvec_push(&vec, "foo");
> ++ check_strvec(&vec, "foo", NULL);
> ++
> ++ strvec_push(&vec, "bar");
> ++ check_strvec(&vec, "foo", "bar", NULL);
> ++
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__pushf(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushf(&vec, "foo: %d", 1);
> -@@ t/unit-tests/strvec.c: static void t_pushf(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_pushl(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushf(&vec, "foo: %d", 1);
> ++ check_strvec(&vec, "foo: 1", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__pushl(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_pushl(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_pushv(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ check_strvec(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__pushv(void)
> - {
> - const char *strings[] = {
> - "foo", "bar", "baz", NULL,
> -@@ t/unit-tests/strvec.c: static void t_pushv(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_replace_at_head(void)
> ++{
> ++ const char *strings[] = {
> ++ "foo", "bar", "baz", NULL,
> ++ };
> ++ struct strvec vec = STRVEC_INIT;
> ++
> ++ strvec_pushv(&vec, strings);
> ++ check_strvec(&vec, "foo", "bar", "baz", NULL);
> ++
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__replace_at_head(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_replace_at_head(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_replace_at_tail(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_replace(&vec, 0, "replaced");
> ++ check_strvec(&vec, "replaced", "bar", "baz", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__replace_at_tail(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_replace_at_tail(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_replace_in_between(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_replace(&vec, 2, "replaced");
> ++ check_strvec(&vec, "foo", "bar", "replaced", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__replace_in_between(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_replace_in_between(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_replace_with_substring(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_replace(&vec, 1, "replaced");
> ++ check_strvec(&vec, "foo", "replaced", "baz", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__replace_with_substring(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", NULL);
> -@@ t/unit-tests/strvec.c: static void t_replace_with_substring(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_remove_at_head(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", NULL);
> ++ strvec_replace(&vec, 0, vec.v[0] + 1);
> ++ check_strvec(&vec, "oo", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__remove_at_head(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_remove_at_head(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_remove_at_tail(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_remove(&vec, 0);
> ++ check_strvec(&vec, "bar", "baz", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__remove_at_tail(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_remove_at_tail(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_remove_in_between(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_remove(&vec, 2);
> ++ check_strvec(&vec, "foo", "bar", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__remove_in_between(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_remove_in_between(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_pop_empty_array(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_remove(&vec, 1);
> ++ check_strvec(&vec, "foo", "baz", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__pop_empty_array(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pop(&vec);
> -@@ t/unit-tests/strvec.c: static void t_pop_empty_array(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_pop_non_empty_array(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pop(&vec);
> ++ check_strvec(&vec, NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__pop_non_empty_array(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> -@@ t/unit-tests/strvec.c: static void t_pop_non_empty_array(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_split_empty_string(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_pop(&vec);
> ++ check_strvec(&vec, "foo", "bar", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__split_empty_string(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "");
> -@@ t/unit-tests/strvec.c: static void t_split_empty_string(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_split_single_item(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_split(&vec, "");
> ++ check_strvec(&vec, NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__split_single_item(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "foo");
> -@@ t/unit-tests/strvec.c: static void t_split_single_item(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_split_multiple_items(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_split(&vec, "foo");
> ++ check_strvec(&vec, "foo", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__split_multiple_items(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "foo bar baz");
> -@@ t/unit-tests/strvec.c: static void t_split_multiple_items(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_split_whitespace_only(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_split(&vec, "foo bar baz");
> ++ check_strvec(&vec, "foo", "bar", "baz", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__split_whitespace_only(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, " \t\n");
> -@@ t/unit-tests/strvec.c: static void t_split_whitespace_only(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_split_multiple_consecutive_whitespaces(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_split(&vec, " \t\n");
> ++ check_strvec(&vec, NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__split_multiple_consecutive_whitespaces(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - strvec_split(&vec, "foo\n\t bar");
> -@@ t/unit-tests/strvec.c: static void t_split_multiple_consecutive_whitespaces(void)
> - strvec_clear(&vec);
> - }
> -
> --static void t_detach(void)
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ strvec_split(&vec, "foo\n\t bar");
> ++ check_strvec(&vec, "foo", "bar", NULL);
> ++ strvec_clear(&vec);
> ++}
> ++
> +void test_strvec__detach(void)
> - {
> - struct strvec vec = STRVEC_INIT;
> - const char **detached;
> -@@ t/unit-tests/strvec.c: static void t_detach(void)
> - strvec_push(&vec, "foo");
> -
> - detached = strvec_detach(&vec);
> -- check_str(detached[0], "foo");
> -- check_pointer_eq(detached[1], NULL);
> ++{
> ++ struct strvec vec = STRVEC_INIT;
> ++ const char **detached;
> ++
> ++ strvec_push(&vec, "foo");
> ++
> ++ detached = strvec_detach(&vec);
> + cl_assert_equal_s(detached[0], "foo");
> + cl_assert_equal_p(detached[1], NULL);
> -
> -- check_pointer_eq(vec.v, empty_strvec);
> -- check_uint(vec.nr, ==, 0);
> -- check_uint(vec.alloc, ==, 0);
> ++
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> -
> - free((char *) detached[0]);
> - free(detached);
> - }
> ++
> ++ free((char *) detached[0]);
> ++ free(detached);
> ++}
> +
> + ## t/unit-tests/t-strvec.c (deleted) ##
> +@@
> +-#include "test-lib.h"
> +-#include "strbuf.h"
> +-#include "strvec.h"
> +-
> +-#define check_strvec(vec, ...) \
> +- do { \
> +- const char *expect[] = { __VA_ARGS__ }; \
> +- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
> +- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
> +- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
> +- check_uint((vec)->nr, <=, (vec)->alloc)) { \
> +- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
> +- if (!check_str((vec)->v[i], expect[i])) { \
> +- test_msg(" i: %"PRIuMAX, \
> +- (uintmax_t)i); \
> +- break; \
> +- } \
> +- } \
> +- } \
> +- } while (0)
> -
> -int cmd_main(int argc, const char **argv)
> -{
> -- TEST(t_static_init(), "static initialization");
> -- TEST(t_dynamic_init(), "dynamic initialization");
> -- TEST(t_clear(), "clear");
> -- TEST(t_push(), "push");
> -- TEST(t_pushf(), "pushf");
> -- TEST(t_pushl(), "pushl");
> -- TEST(t_pushv(), "pushv");
> -- TEST(t_replace_at_head(), "replace at head");
> -- TEST(t_replace_in_between(), "replace in between");
> -- TEST(t_replace_at_tail(), "replace at tail");
> -- TEST(t_replace_with_substring(), "replace with substring");
> -- TEST(t_remove_at_head(), "remove at head");
> -- TEST(t_remove_in_between(), "remove in between");
> -- TEST(t_remove_at_tail(), "remove at tail");
> -- TEST(t_pop_empty_array(), "pop with empty array");
> -- TEST(t_pop_non_empty_array(), "pop with non-empty array");
> -- TEST(t_split_empty_string(), "split empty string");
> -- TEST(t_split_single_item(), "split single item");
> -- TEST(t_split_multiple_items(), "split multiple items");
> -- TEST(t_split_whitespace_only(), "split whitespace only");
> -- TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
> -- TEST(t_detach(), "detach");
> +- if_test ("static initialization") {
> +- struct strvec vec = STRVEC_INIT;
> +- check_pointer_eq(vec.v, empty_strvec);
> +- check_uint(vec.nr, ==, 0);
> +- check_uint(vec.alloc, ==, 0);
> +- }
> +-
> +- if_test ("dynamic initialization") {
> +- struct strvec vec;
> +- strvec_init(&vec);
> +- check_pointer_eq(vec.v, empty_strvec);
> +- check_uint(vec.nr, ==, 0);
> +- check_uint(vec.alloc, ==, 0);
> +- }
> +-
> +- if_test ("clear") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_push(&vec, "foo");
> +- strvec_clear(&vec);
> +- check_pointer_eq(vec.v, empty_strvec);
> +- check_uint(vec.nr, ==, 0);
> +- check_uint(vec.alloc, ==, 0);
> +- }
> +-
> +- if_test ("push") {
> +- struct strvec vec = STRVEC_INIT;
> +-
> +- strvec_push(&vec, "foo");
> +- check_strvec(&vec, "foo", NULL);
> +-
> +- strvec_push(&vec, "bar");
> +- check_strvec(&vec, "foo", "bar", NULL);
> +-
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("pushf") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushf(&vec, "foo: %d", 1);
> +- check_strvec(&vec, "foo: 1", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("pushl") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- check_strvec(&vec, "foo", "bar", "baz", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("pushv") {
> +- const char *strings[] = {
> +- "foo", "bar", "baz", NULL,
> +- };
> +- struct strvec vec = STRVEC_INIT;
> +-
> +- strvec_pushv(&vec, strings);
> +- check_strvec(&vec, "foo", "bar", "baz", NULL);
> +-
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("replace at head") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_replace(&vec, 0, "replaced");
> +- check_strvec(&vec, "replaced", "bar", "baz", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("replace at tail") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_replace(&vec, 2, "replaced");
> +- check_strvec(&vec, "foo", "bar", "replaced", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("replace in between") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_replace(&vec, 1, "replaced");
> +- check_strvec(&vec, "foo", "replaced", "baz", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("replace with substring") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", NULL);
> +- strvec_replace(&vec, 0, vec.v[0] + 1);
> +- check_strvec(&vec, "oo", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("remove at head") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_remove(&vec, 0);
> +- check_strvec(&vec, "bar", "baz", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("remove at tail") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_remove(&vec, 2);
> +- check_strvec(&vec, "foo", "bar", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("remove in between") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_remove(&vec, 1);
> +- check_strvec(&vec, "foo", "baz", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("pop with empty array") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pop(&vec);
> +- check_strvec(&vec, NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("pop with non-empty array") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> +- strvec_pop(&vec);
> +- check_strvec(&vec, "foo", "bar", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("split empty string") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_split(&vec, "");
> +- check_strvec(&vec, NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("split single item") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_split(&vec, "foo");
> +- check_strvec(&vec, "foo", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("split multiple items") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_split(&vec, "foo bar baz");
> +- check_strvec(&vec, "foo", "bar", "baz", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("split whitespace only") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_split(&vec, " \t\n");
> +- check_strvec(&vec, NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("split multiple consecutive whitespaces") {
> +- struct strvec vec = STRVEC_INIT;
> +- strvec_split(&vec, "foo\n\t bar");
> +- check_strvec(&vec, "foo", "bar", NULL);
> +- strvec_clear(&vec);
> +- }
> +-
> +- if_test ("detach") {
> +- struct strvec vec = STRVEC_INIT;
> +- const char **detached;
> +-
> +- strvec_push(&vec, "foo");
> +-
> +- detached = strvec_detach(&vec);
> +- check_str(detached[0], "foo");
> +- check_pointer_eq(detached[1], NULL);
> +-
> +- check_pointer_eq(vec.v, empty_strvec);
> +- check_uint(vec.nr, ==, 0);
> +- check_uint(vec.alloc, ==, 0);
> +-
> +- free((char *) detached[0]);
> +- free(detached);
> +- }
> +-
> - return test_done();
> -}
> -
> - ## t/unit-tests/unit-test.c ##
> -@@ t/unit-tests/unit-test.c: int cmd_main(int argc, const char **argv)
> - int ret;
> -
> - /* Append the "-t" flag such that the tests generate TAP output. */
> -- ALLOC_ARRAY(argv_copy, argc + 2);
> -+ ALLOC_ARRAY(argv_copy, argc + 1);
> - COPY_ARRAY(argv_copy, argv, argc);
> - argv_copy[argc++] = "-t";
> -- argv_copy[argc] = NULL;
> -
> - ret = clar_test(argc, (char **) argv_copy);
> -
> 9: ca09d19fd51 ! 12: 1ac2e48a7f2 t/unit-tests: convert ctype tests to use clar
> @@ Commit message
> t/unit-tests: convert ctype tests to use clar
>
> Convert the ctype tests to use the new clar unit testing framework.
> + Introduce a new function `cl_failf()` that allows us to print a
> + formatted error message, which we can use to point out which of the
> + characters was classified incorrectly. This results in output like this
> + on failure:
> +
> + # start of suite 1: ctype
> + ok 1 - ctype::isspace
> + not ok 2 - ctype::isdigit
> + ---
> + reason: |
> + Test failed.
> + 0x61 is classified incorrectly
> + at:
> + file: 't/unit-tests/ctype.c'
> + line: 38
> + function: 'test_ctype__isdigit'
> + ---
> + ok 3 - ctype::isalpha
> + ok 4 - ctype::isalnum
> + ok 5 - ctype::is_glob_special
> + ok 6 - ctype::is_regex_special
> + ok 7 - ctype::is_pathspec_magic
> + ok 8 - ctype::isascii
> + ok 9 - ctype::islower
> + ok 10 - ctype::isupper
> + ok 11 - ctype::iscntrl
> + ok 12 - ctype::ispunct
> + ok 13 - ctype::isxdigit
> + ok 14 - ctype::isprint
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>
> @@ Makefile: THIRD_PARTY_SOURCES += sha1dc/%
> -UNIT_TEST_PROGRAMS += t-ctype
> UNIT_TEST_PROGRAMS += t-example-decorate
> UNIT_TEST_PROGRAMS += t-hash
> - UNIT_TEST_PROGRAMS += t-mem-pool
> + UNIT_TEST_PROGRAMS += t-hashmap
>
> ## t/unit-tests/t-ctype.c => t/unit-tests/ctype.c ##
> @@
> @@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
> size_t len = ARRAY_SIZE(string) - 1 + \
> BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
> BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
> -- int skip = test__run_begin(); \
> -- if (!skip) { \
> +- if_test (#class " works") { \
> - for (int i = 0; i < 256; i++) { \
> - if (!check_int(class(i), ==, !!memchr(string, i, len)))\
> - test_msg(" i: 0x%02x", i); \
> - } \
> - check(!class(EOF)); \
> - } \
> -- test__run_end(!skip, TEST_LOCATION(), #class " works"); \
> + for (int i = 0; i < 256; i++) \
> -+ cl_assert_equal_i(class(i), !!memchr(string, i, len)); \
> ++ if (class(i) != !!memchr(string, i, len)) \
> ++ cl_failf("0x%02x is classified incorrectly", i); \
> + cl_assert(!class(EOF)); \
> } while (0)
>
> @@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
> +{
> + TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
> }
> +
> + ## t/unit-tests/unit-test.h ##
> +@@
> + #include "git-compat-util.h"
> + #include "clar/clar.h"
> + #include "clar-decls.h"
> ++#include "strbuf.h"
> ++
> ++#define cl_failf(fmt, ...) do { \
> ++ char *desc = xstrfmt(fmt, __VA_ARGS__); \
> ++ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
> ++ free(desc); \
> ++} while (0)
> -: ----------- > 13: 131036c398e clar: add CMake support
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 00/13] Introduce clar testing framework
2024-08-28 13:18 ` [PATCH v6 00/13] Introduce clar testing framework Phillip Wood
@ 2024-08-28 14:03 ` Patrick Steinhardt
2024-08-28 14:58 ` phillip.wood123
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-08-28 14:03 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Wed, Aug 28, 2024 at 02:18:49PM +0100, Phillip Wood wrote:
> On 20/08/2024 15:02, Patrick Steinhardt wrote:
> > Hi,
> >
> > this is another version of my patch series that introduces the clar
> > testing framework for our C unit tests.
>
> I've left some comments on the code, most of them are points I made in the
> last round that received no response. My main concern is that the assertions
> offered by clar are not as convinent as the check_* macros.
Did you have a look at my reply at <ZsSTOczuGhT5zwoV@tanuki>, where I
responded to these concerns?
In summary: I'm aware that this is still a rough edge. I'd be happy to
follow up on this and improve usability of the assertions, but doing it
likely is a bit more involved, mostly because I want to upstream all
changes in this context. So I'd rather want to land a basic version
first, and then I'd iterate and improve asserts.
> What's the plan for converting our current tests if this gets merged? If we
> were to add wrappers that provide check_int() etc. that would greatly
> simplify the conversion. I think it would offer a more ergonomic api for
> writing new tests than the verbose and non-typesafe cl_assert_equal_i() and
> friends.
My plan would first be to let things cook for a bit while I sort out the
rough spots upstream. Once done and once we are sufficiently sure that
this is the direction to go I'm happy to do the conversion myself.
Whether we want to have wrappers... I dunno, I don't think the names are
all that bad. They have a clear namespace and say rather directly what
they are doing, which I value more than briefness (to a certain extent,
of course). The type safety is another topic though, and something I
will aim to address.
Thanks!
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v6 00/13] Introduce clar testing framework
2024-08-28 14:03 ` Patrick Steinhardt
@ 2024-08-28 14:58 ` phillip.wood123
0 siblings, 0 replies; 172+ messages in thread
From: phillip.wood123 @ 2024-08-28 14:58 UTC (permalink / raw)
To: Patrick Steinhardt, phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 28/08/2024 15:03, Patrick Steinhardt wrote:
> On Wed, Aug 28, 2024 at 02:18:49PM +0100, Phillip Wood wrote:
>> On 20/08/2024 15:02, Patrick Steinhardt wrote:
>>> Hi,
>>>
>>> this is another version of my patch series that introduces the clar
>>> testing framework for our C unit tests.
>>
>> I've left some comments on the code, most of them are points I made in the
>> last round that received no response. My main concern is that the assertions
>> offered by clar are not as convinent as the check_* macros.
>
> Did you have a look at my reply at <ZsSTOczuGhT5zwoV@tanuki>, where I
> responded to these concerns?
Oh sorry I'd completely missed that mail (it came in while I was off
line and I failed to notice it).
> In summary: I'm aware that this is still a rough edge. I'd be happy to
> follow up on this and improve usability of the assertions, but doing it
> likely is a bit more involved, mostly because I want to upstream all
> changes in this context. So I'd rather want to land a basic version
> first, and then I'd iterate and improve asserts.
That sounds reasonable
>> What's the plan for converting our current tests if this gets merged? If we
>> were to add wrappers that provide check_int() etc. that would greatly
>> simplify the conversion. I think it would offer a more ergonomic api for
>> writing new tests than the verbose and non-typesafe cl_assert_equal_i() and
>> friends.
>
> My plan would first be to let things cook for a bit while I sort out the
> rough spots upstream. Once done and once we are sufficiently sure that
> this is the direction to go I'm happy to do the conversion myself.
>
> Whether we want to have wrappers... I dunno, I don't think the names are
> all that bad. They have a clear namespace and say rather directly what
> they are doing, which I value more than briefness (to a certain extent,
> of course).
One could argue the check_* are namespaced by "check". I find writing
unit tests in C is pretty tedious and having to type cl_assert_equal_?
just adds to that.
> The type safety is another topic though, and something I
> will aim to address.
Thanks, it sounds like we're more or less on the same page
Best Wishes
Phillip
> Thanks!
>
> Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v7 00/14] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (9 preceding siblings ...)
2024-08-20 14:02 ` [PATCH v6 00/13] " Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (14 more replies)
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
11 siblings, 15 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi,
this is the seventh version fo my patch series that introduces the clar
unit testing framework.
Changes compared to v6:
- Add some explanations for why we introduce clar in the first place
to the second commit message.
- Split out implementation of the test driver, that is the main
function, into a separate commit.
- Reimplement argument parsing ourselves. This allows more flexibility
and gets rid of some options that do not make sense for us.
- Add an empty line between declarations and code.
- Improve the test messages in ctype tests to also mention our
expectations.
- Adapt `cl_failf()` to not use `xstrfmt()`, but `snprintf()` with a
static buffer instead.
Thanks!
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64
clar(win32): avoid compile error due to unused `fs_copy()`
clar: stop including `shellapi.h` unnecessarily
clar: add CMake support
Patrick Steinhardt (10):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework
t/unit-tests: implement test driver
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
contrib/buildsystems/CMakeLists.txt | 53 ++
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/strvec.c | 241 ++++++
t/unit-tests/t-strvec.c | 211 ------
t/unit-tests/unit-test.c | 45 ++
t/unit-tests/unit-test.h | 10 +
31 files changed, 3457 insertions(+), 234 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v6:
1: e48a6461137 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: 1710e9f9ff7 ! 2: 55a9b46e65f t: import the clar unit testing framework
@@ Metadata
## Commit message ##
t: import the clar unit testing framework
+ Our unit testing framework is a homegrown solution. While it supports
+ most of our needs, it is likely that the volume of unit tests will grow
+ quite a bit in the future such that we can exercise low-level subsystems
+ directly. This surfaces several shortcomings that the current solution
+ has:
+
+ - There is no way to run only one specific tests. While some of our
+ unit tests wire this up manually, others don't. In general, it
+ requires quite a bit of boilerplate to get this set up correctly.
+
+ - Failures do not cause a test to stop execution directly. Instead,
+ the test author needs to return manually whenever an assertion
+ fails. This is rather verbose and is not done correctly in most of
+ our unit tests.
+
+ - Wiring up a new testcase requires both implementing the test
+ function and calling it in the respective test suite's main
+ function, which is creating code duplication.
+
+ We can of course fix all of these issues ourselves, but that feels
+ rather pointless when there are already so many unit testing frameworks
+ out there that have those features.
+
+ We line out some requirements for any unit testing framework in
+ "Documentation/technical/unit-tests.txt". The "clar" unit testing
+ framework, which isn't listed in that table yet, ticks many of the
+ boxes:
+
+ - It is licensed under ISC, which is compatible.
+
+ - It is easily vendorable because it is rather tiny at around 1200
+ lines of code.
+
+ - It is easily hackable due to the same reason.
+
+ - It has TAP support.
+
+ - It has skippable tests.
+
+ - It preprocesses test files in order to extract test functions, which
+ then get wired up automatically.
+
+ While it's not perfect, the fact that clar originates from the libgit2
+ project means that it should be rather easy for us to collaborate with
+ upstream to plug any gaps.
+
Import the clar unit testing framework at commit 1516124 (Merge pull
request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
will be wired up in subsequent commits.
3: 5c21aa87aa2 = 3: f24401f0a87 t/clar: fix compatibility with NonStop
4: 06d2bce0d82 = 4: 658a601c541 clar: avoid compile error with mingw-w64
5: f88b3421a09 = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()`
6: 5fb4c55be33 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily
7: e0dcbd5ca83 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H
8: 77a03f8df70 = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers
9: c91dd7327e3 = 9: 7d0f494850a Makefile: do not use sparse on third-party sources
10: 115c15aa9ae ! 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework
@@ Commit message
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
+ The test driver "unit-test.c" is an empty stub for now. It will get
+ implemented in the next commit.
+
Signed-off-by: Patrick Steinhardt <ps@pks.im>
## .gitignore ##
@@ t/unit-tests/unit-test.c (new)
@@
+#include "unit-test.h"
+
-+int cmd_main(int argc, const char **argv)
++int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
-+ const char **argv_copy;
-+ int ret;
-+
-+ /* Append the "-t" flag such that the tests generate TAP output. */
-+ ALLOC_ARRAY(argv_copy, argc + 2);
-+ COPY_ARRAY(argv_copy, argv, argc);
-+ argv_copy[argc++] = "-t";
-+ argv_copy[argc] = NULL;
-+
-+ ret = clar_test(argc, (char **) argv_copy);
-+
-+ free(argv_copy);
-+ return ret;
++ return 0;
+}
## t/unit-tests/unit-test.h (new) ##
-: ----------- > 11: 8bd5b3e2b29 t/unit-tests: implement test driver
11: b3b8df04872 ! 12: 3c3b9eacdfb t/unit-tests: convert strvec tests to use clar
@@ Commit message
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
- running that binary, it generates TAP output:
+ running that binary with an injected error, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
@@ Commit message
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
- ok 4 - strvec::push
- ok 5 - strvec::pushft_pushf
+ not ok 4 - strvec::push
+ ---
+ reason: |
+ String mismatch: (&vec)->v[i] != expect[i]
+ 'foo' != 'fo' (at byte 2)
+ at:
+ file: 't/unit-tests/strvec.c'
+ line: 48
+ function: 'test_strvec__push'
+ ---
+ ok 5 - strvec::pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
@@ t/unit-tests/strvec.c (new)
+#define check_strvec(vec, ...) \
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
-+ cl_assert(ARRAY_SIZE(expect) > 0); \
-+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
-+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
++ size_t expect_len = ARRAY_SIZE(expect); \
++ cl_assert(expect_len > 0); \
++ cl_assert_equal_p(expect[expect_len - 1], NULL); \
++ cl_assert_equal_i((vec)->nr, expect_len - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
-+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
++ for (size_t i = 0; i < expect_len; i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
+
+void test_strvec__init(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__dynamic_init(void)
+{
+ struct strvec vec;
++
+ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__clear(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_push(&vec, "foo");
+ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pushf(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushf(&vec, "foo: %d", 1);
+ check_strvec(&vec, "foo: 1", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pushl(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__replace_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 0, "replaced");
+ check_strvec(&vec, "replaced", "bar", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__replace_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 1, "replaced");
+ check_strvec(&vec, "foo", "replaced", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__replace_with_substring(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", NULL);
+ strvec_replace(&vec, 0, vec.v[0] + 1);
+ check_strvec(&vec, "oo", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__remove_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 0);
+ check_strvec(&vec, "bar", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__remove_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 2);
+ check_strvec(&vec, "foo", "bar", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__remove_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 1);
+ check_strvec(&vec, "foo", "baz", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pop_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pop(&vec);
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__pop_non_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_pop(&vec);
+ check_strvec(&vec, "foo", "bar", NULL);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_empty_string(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_single_item(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_multiple_items(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "foo bar baz");
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_whitespace_only(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, " \t\n");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
@@ t/unit-tests/strvec.c (new)
+void test_strvec__split_multiple_consecutive_whitespaces(void)
+{
+ struct strvec vec = STRVEC_INIT;
++
+ strvec_split(&vec, "foo\n\t bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
12: 1ac2e48a7f2 ! 13: c8360db2f86 t/unit-tests: convert ctype tests to use clar
@@ Commit message
on failure:
# start of suite 1: ctype
- ok 1 - ctype::isspace
- not ok 2 - ctype::isdigit
+ not ok 1 - ctype::isspace
---
reason: |
Test failed.
- 0x61 is classified incorrectly
+ 0x0d is classified incorrectly: expected 0, got 1
at:
file: 't/unit-tests/ctype.c'
- line: 38
- function: 'test_ctype__isdigit'
+ line: 36
+ function: 'test_ctype__isspace'
---
+ ok 2 - ctype::isdigit
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
@@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
-- } \
-+ for (int i = 0; i < 256; i++) \
-+ if (class(i) != !!memchr(string, i, len)) \
-+ cl_failf("0x%02x is classified incorrectly", i); \
++ for (int i = 0; i < 256; i++) { \
++ int actual = class(i), expect = !!memchr(string, i, len); \
++ if (actual != expect) \
++ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \
++ i, expect, actual); \
+ } \
+ cl_assert(!class(EOF)); \
} while (0)
@@ t/unit-tests/unit-test.h
+#include "strbuf.h"
+
+#define cl_failf(fmt, ...) do { \
-+ char *desc = xstrfmt(fmt, __VA_ARGS__); \
++ char desc[4096]; \
++ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
+ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
-+ free(desc); \
+} while (0)
13: 131036c398e = 14: d51c146cd9d clar: add CMake support
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v7 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 02/14] t: import the clar unit testing framework Patrick Steinhardt
` (13 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06fb..d2212de0b78 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b4..63328ac630c 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 02/14] t: import the clar unit testing framework
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:47 ` Eric Sunshine
2024-09-03 9:14 ` [PATCH v7 03/14] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (12 subsequent siblings)
14 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Our unit testing framework is a homegrown solution. While it supports
most of our needs, it is likely that the volume of unit tests will grow
quite a bit in the future such that we can exercise low-level subsystems
directly. This surfaces several shortcomings that the current solution
has:
- There is no way to run only one specific tests. While some of our
unit tests wire this up manually, others don't. In general, it
requires quite a bit of boilerplate to get this set up correctly.
- Failures do not cause a test to stop execution directly. Instead,
the test author needs to return manually whenever an assertion
fails. This is rather verbose and is not done correctly in most of
our unit tests.
- Wiring up a new testcase requires both implementing the test
function and calling it in the respective test suite's main
function, which is creating code duplication.
We can of course fix all of these issues ourselves, but that feels
rather pointless when there are already so many unit testing frameworks
out there that have those features.
We line out some requirements for any unit testing framework in
"Documentation/technical/unit-tests.txt". The "clar" unit testing
framework, which isn't listed in that table yet, ticks many of the
boxes:
- It is licensed under ISC, which is compatible.
- It is easily vendorable because it is rather tiny at around 1200
lines of code.
- It is easily hackable due to the same reason.
- It has TAP support.
- It has skippable tests.
- It preprocesses test files in order to extract test functions, which
then get wired up automatically.
While it's not perfect, the fact that clar originates from the libgit2
project means that it should be rather easy for us to collaborate with
upstream to plug any gaps.
Import the clar unit testing framework at commit 1516124 (Merge pull
request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 153 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2943 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb19..5a432b7b29c 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index a87e18b317d..28742a60964 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 00000000000..b1ac2de460a
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 00000000000..8983817f0c9
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 00000000000..a8961c5f10f
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 00000000000..3fc2c768158
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 00000000000..8c22382bd56
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 00000000000..6ec6423484d
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 00000000000..3e39890bd3e
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 00000000000..c17e2f693bd
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 00000000000..7c177f35258
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,153 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 00000000000..4dd352e28b8
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 00000000000..80996ac3e71
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 00000000000..a477d0c40ca
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 00000000000..93c6b2ad32c
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 00000000000..0fcaa639aa8
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 00000000000..59e56ad255b
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 00000000000..a4d91b72fa8
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 00000000000..220f4aa98a7
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 00000000000..faa1209262f
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v7 02/14] t: import the clar unit testing framework
2024-09-03 9:14 ` [PATCH v7 02/14] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-09-03 9:47 ` Eric Sunshine
2024-09-04 6:38 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Eric Sunshine @ 2024-09-03 9:47 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood, Josh Steadmon, rsbecker, Edward Thomson,
Johannes Schindelin
On Tue, Sep 3, 2024 at 5:15 AM Patrick Steinhardt <ps@pks.im> wrote:>
> Our unit testing framework is a homegrown solution. While it supports
> most of our needs, it is likely that the volume of unit tests will grow
> quite a bit in the future such that we can exercise low-level subsystems
> directly. This surfaces several shortcomings that the current solution
> has:
>
> - There is no way to run only one specific tests. While some of our
> unit tests wire this up manually, others don't. In general, it
> requires quite a bit of boilerplate to get this set up correctly.
>
> - Failures do not cause a test to stop execution directly. Instead,
> the test author needs to return manually whenever an assertion
> fails. This is rather verbose and is not done correctly in most of
> our unit tests.
>
> - Wiring up a new testcase requires both implementing the test
> function and calling it in the respective test suite's main
> function, which is creating code duplication.
>
> We can of course fix all of these issues ourselves, but that feels
> rather pointless when there are already so many unit testing frameworks
> out there that have those features.
>
> We line out some requirements for any unit testing framework in
Perhaps you meant s/line out/outline/ ?
(Not worth a reroll.)
> "Documentation/technical/unit-tests.txt". The "clar" unit testing
> framework, which isn't listed in that table yet, ticks many of the
> boxes:
>
> - It is licensed under ISC, which is compatible.
>
> - It is easily vendorable because it is rather tiny at around 1200
> lines of code.
>
> - It is easily hackable due to the same reason.
>
> - It has TAP support.
>
> - It has skippable tests.
>
> - It preprocesses test files in order to extract test functions, which
> then get wired up automatically.
>
> While it's not perfect, the fact that clar originates from the libgit2
> project means that it should be rather easy for us to collaborate with
> upstream to plug any gaps.
>
> Import the clar unit testing framework at commit 1516124 (Merge pull
> request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
> will be wired up in subsequent commits.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v7 02/14] t: import the clar unit testing framework
2024-09-03 9:47 ` Eric Sunshine
@ 2024-09-04 6:38 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 6:38 UTC (permalink / raw)
To: Eric Sunshine
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Phillip Wood, Josh Steadmon, rsbecker, Edward Thomson,
Johannes Schindelin
On Tue, Sep 03, 2024 at 05:47:04AM -0400, Eric Sunshine wrote:
> On Tue, Sep 3, 2024 at 5:15 AM Patrick Steinhardt <ps@pks.im> wrote:>
> > Our unit testing framework is a homegrown solution. While it supports
> > most of our needs, it is likely that the volume of unit tests will grow
> > quite a bit in the future such that we can exercise low-level subsystems
> > directly. This surfaces several shortcomings that the current solution
> > has:
> >
> > - There is no way to run only one specific tests. While some of our
> > unit tests wire this up manually, others don't. In general, it
> > requires quite a bit of boilerplate to get this set up correctly.
> >
> > - Failures do not cause a test to stop execution directly. Instead,
> > the test author needs to return manually whenever an assertion
> > fails. This is rather verbose and is not done correctly in most of
> > our unit tests.
> >
> > - Wiring up a new testcase requires both implementing the test
> > function and calling it in the respective test suite's main
> > function, which is creating code duplication.
> >
> > We can of course fix all of these issues ourselves, but that feels
> > rather pointless when there are already so many unit testing frameworks
> > out there that have those features.
> >
> > We line out some requirements for any unit testing framework in
>
> Perhaps you meant s/line out/outline/ ?
>
> (Not worth a reroll.)
Hum. Yeah, this is German grammar leaking into English. Anyway, TIL what
"to line out" means :) Thanks!
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v7 03/14] t/clar: fix compatibility with NonStop
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 02/14] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 04/14] clar: avoid compile error with mingw-w64 Patrick Steinhardt
` (11 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f35258..e25057b7c49 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 04/14] clar: avoid compile error with mingw-w64
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (2 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 03/14] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 05/14] clar(win32): avoid compile error due to unused `fs_copy()` Patrick Steinhardt
` (10 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When using mingw-w64 to compile the code, and using `_stat()`, it is
necessary to use `struct _stat`, too, and not `struct stat` (as the
latter is incompatible with the "dashed" version because it is limited
to 32-bit time types for backwards compatibility).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index 3fc2c768158..e2ebe551d38 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -68,7 +68,7 @@
# define PRIxZ "Ix"
# endif
-# if defined(_MSC_VER) || defined(__MINGW32__)
+# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
typedef struct stat STAT_T;
# else
typedef struct _stat STAT_T;
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 05/14] clar(win32): avoid compile error due to unused `fs_copy()`
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (3 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 04/14] clar: avoid compile error with mingw-w64 Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 06/14] clar: stop including `shellapi.h` unnecessarily Patrick Steinhardt
` (9 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When CLAR_FIXTURE_PATH is unset, the `fs_copy()` function seems not to
be used. But it is declared as `static`, and GCC does not like that,
complaining that it should not be declared/defined to begin with.
We could mark this function as (potentially) unused by following the
`MAYBE_UNUSED` pattern from Git's `git-compat-util.h`. However, this is
a GCC-only construct that is not understood by Visual C. Besides, `clar`
does not use that pattern at all.
Instead, let's use the `((void)SYMBOL);` pattern that `clar` already
uses elsewhere; This avoids the compile error by sorta kinda make the
function used after a fashion.
Note: GCC 14.x (which Git for Windows' SDK already uses) is able to
figure out that this function is unused even though there are recursive
calls between `fs_copy()` and `fs_copydir_helper()`; Earlier GCC
versions do not detect that, and therefore the issue has been hidden
from the regular Linux CI builds (where GCC 14.x is not yet used). That
is the reason why this change is only made in the Windows-specific
portion of `t/unit-tests/clar/clar/fs.h`.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/fs.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
index 3e39890bd3e..8b206179fc4 100644
--- a/t/unit-tests/clar/clar/fs.h
+++ b/t/unit-tests/clar/clar/fs.h
@@ -297,6 +297,8 @@ cl_fs_cleanup(void)
{
#ifdef CLAR_FIXTURE_PATH
fs_rm(fixture_path(_clar_path, "*"));
+#else
+ ((void)fs_copy); /* unused */
#endif
}
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 06/14] clar: stop including `shellapi.h` unnecessarily
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (4 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 05/14] clar(win32): avoid compile error due to unused `fs_copy()` Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 07/14] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
` (8 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The `shellapi.h` header was included as of
https://github.com/clar-test/clar/commit/136e763211aa, to have
`SHFileOperation()` declared so that it could be called.
However, https://github.com/clar-test/clar/commit/5ce31b69b525 removed
that call, and therefore that `#include <shellapi.h>` is unnecessary.
It is also unwanted in Git because this project uses a subset of Git for
Windows' SDK in its CI builds that (for bandwidth reasons) excludes tons
of header files, including `shellapi.h`.
So let's remove it.
Note: Since the `windows.h` header would include `shellapi.h` anyway, we
also define `WIN32_LEAN_AND_MEAN` to avoid this and similar other
unnecessary includes before including `windows.h`.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index e2ebe551d38..cef0f023c24 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -19,9 +19,9 @@
#include <sys/stat.h>
#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <io.h>
-# include <shellapi.h>
# include <direct.h>
# define _MAIN_CC __cdecl
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 07/14] Makefile: fix sparse dependency on GENERATED_H
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (5 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 06/14] clar: stop including `shellapi.h` unnecessarily Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:14 ` [PATCH v7 08/14] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
` (7 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The "check" Makefile target is essentially an alias around the "sparse"
target. The one difference though is that it will tell users to instead
run the "test" target in case they do not have sparse(1) installed, as
chances are high that they wanted to execute the test suite rather than
doing semantic checks.
But even though the "check" target ultimately just ends up executing
`make sparse`, it still depends on our generated headers. This does not
make any sense though: they are irrelevant for the "test" target advice,
and if these headers are required for the "sparse" target they must be
declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as
concurrent builds may then end up generating the headers and running
sparse concurrently. Instead, we make them a dependency of the specific
objects. While that is overly broad, it does ensure correct ordering.
The alternative, specifying which file depends on what generated header
explicitly, feels rather unmaintainable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 28742a60964..efd305ab358 100644
--- a/Makefile
+++ b/Makefile
@@ -3254,7 +3254,7 @@ check-sha1:: t/helper/test-tool$X
SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
-$(SP_OBJ): %.sp: %.c %.o
+$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
-Wsparse-error \
$(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3295,7 +3295,7 @@ style:
git clang-format --style file --diff --extensions c,h
.PHONY: check
-check: $(GENERATED_H)
+check:
@if sparse; \
then \
echo >&2 "Use 'make sparse' instead"; \
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 08/14] Makefile: make hdr-check depend on generated headers
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (6 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 07/14] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
@ 2024-09-03 9:14 ` Patrick Steinhardt
2024-09-03 9:15 ` [PATCH v7 09/14] Makefile: do not use sparse on third-party sources Patrick Steinhardt
` (6 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:14 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The "hdr-check" Makefile target compiles each of our headers as a
standalone code unit to ensure that they are not missing any type
declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework,
which will have the effect that some headers start depending on
generated ones. While we could declare that dependency explicitly, it
does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects
depend on all of our generated headers. While again overly broad, it is
easy to maintain and generating headers is not an expensive thing to do
anyway.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index efd305ab358..8c4487dd0c6 100644
--- a/Makefile
+++ b/Makefile
@@ -3284,7 +3284,7 @@ HCC = $(HCO:hco=hcc)
@echo '#include "git-compat-util.h"' >$@
@echo '#include "$<"' >>$@
-$(HCO): %.hco: %.hcc FORCE
+$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE
$(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $<
.PHONY: hdr-check $(HCO)
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 09/14] Makefile: do not use sparse on third-party sources
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (7 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 08/14] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
@ 2024-09-03 9:15 ` Patrick Steinhardt
2024-09-03 9:15 ` [PATCH v7 10/14] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (5 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
We have several third-party sources in our codebase that we have
imported from upstream projects. These sources are mostly excluded from
our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 8c4487dd0c6..81a47b61327 100644
--- a/Makefile
+++ b/Makefile
@@ -3252,7 +3252,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
check-sha1:: t/helper/test-tool$X
t/helper/test-sha1.sh
-SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
+SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS)))
+SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC))
$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 10/14] Makefile: wire up the clar unit testing framework
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (8 preceding siblings ...)
2024-09-03 9:15 ` [PATCH v7 09/14] Makefile: do not use sparse on third-party sources Patrick Steinhardt
@ 2024-09-03 9:15 ` Patrick Steinhardt
2024-09-03 9:15 ` [PATCH v7 11/14] t/unit-tests: implement test driver Patrick Steinhardt
` (4 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
The test driver "unit-test.c" is an empty stub for now. It will get
implemented in the next commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 36 +++++++++++++++++++++---
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 6 ++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 95 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c23..6687bd6db4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 81a47b61327..e38146b5eb0 100644
--- a/Makefile
+++ b/Makefile
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a
GENERATED_H += command-list.h
GENERATED_H += config-list.h
GENERATED_H += hook-list.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar.suite
.PHONY: generated-hdrs
generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3216,7 +3224,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3648,7 +3656,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3704,6 +3712,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3863,7 +3872,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b78..131ffd778fe 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec0..d0632ec7f9e 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 00000000000..ab71ce6c9fc
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 00000000000..3d12cde6dae
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,6 @@
+#include "unit-test.h"
+
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
+ return 0;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 00000000000..66ec2387cc6
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 11/14] t/unit-tests: implement test driver
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (9 preceding siblings ...)
2024-09-03 9:15 ` [PATCH v7 10/14] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-09-03 9:15 ` Patrick Steinhardt
2024-09-04 13:35 ` Phillip Wood
2024-09-03 9:15 ` [PATCH v7 12/14] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (3 subsequent siblings)
14 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The test driver in "unit-test.c" is responsible for setting up our unit
tests and eventually running them. As such, it is also responsible for
parsing the command line arguments.
The clar unit testing framework provides function `clar_test()` that
parses command line arguments and then executes the tests for us. In
theory that would already be sufficient. We have the special requirement
to always generate TAP-formatted output though, so we'd have to always
pass the "-t" argument to clar. Furthermore, some of the options exposed
by clar are ineffective when "-t" is used, but they would still be shown
when the user passes the "-h" parameter to have the clar show its usage.
Implement our own option handling instead of using the one provided by
clar, which gives us greater flexibility in how exactly we set things
up.
We would ideally not use any "normal" code of ours for this such that
the unit testing framework doesn't depend on it working correctly. But
it is somewhat dubious whether we really want to reimplement all of the
option parsing. So for now, let's be pragmatic and reuse it until we
find a good reason in the future why we'd really want to avoid it.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/unit-test.c | 43 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 41 insertions(+), 2 deletions(-)
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 3d12cde6dae..96fa64de71d 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -1,6 +1,45 @@
#include "unit-test.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "strvec.h"
-int cmd_main(int argc UNUSED, const char **argv UNUSED)
+static const char * const unit_test_usage[] = {
+ N_("unit-test [<options>]"),
+ NULL,
+};
+
+int cmd_main(int argc, const char **argv)
{
- return 0;
+ struct string_list run_args = STRING_LIST_INIT_NODUP;
+ struct string_list exclude_args = STRING_LIST_INIT_NODUP;
+ int immediate = 0;
+ struct option options[] = {
+ OPT_BOOL('i', "--immediate", &immediate,
+ N_("immediately exit upon the first failed test")),
+ OPT_STRING_LIST('r', "run", &run_args, N_("name"),
+ N_("run only test suite or individual test <name>")),
+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("name"),
+ N_("exclude test suite <name>")),
+ OPT_END(),
+ };
+ struct strvec args = STRVEC_INIT;
+ int ret;
+
+ argc = parse_options(argc, argv, NULL, options,
+ unit_test_usage, PARSE_OPT_KEEP_ARGV0);
+ if (argc > 1)
+ usagef(_("extra command line parameter '%s'"), argv[0]);
+
+ strvec_push(&args, argv[0]);
+ strvec_push(&args, "-t");
+ for (size_t i = 0; i < run_args.nr; i++)
+ strvec_pushf(&args, "-s%s", run_args.items[i].string);
+ for (size_t i = 0; i < exclude_args.nr; i++)
+ strvec_pushf(&args, "-x%s", exclude_args.items[i].string);
+
+ ret = clar_test(args.nr, (char **) args.v);
+
+ string_list_clear(&run_args, 0);
+ strvec_clear(&args);
+ return ret;
}
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v7 11/14] t/unit-tests: implement test driver
2024-09-03 9:15 ` [PATCH v7 11/14] t/unit-tests: implement test driver Patrick Steinhardt
@ 2024-09-04 13:35 ` Phillip Wood
2024-09-04 14:12 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-09-04 13:35 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 03/09/2024 10:15, Patrick Steinhardt wrote:
> The test driver in "unit-test.c" is responsible for setting up our unit
> tests and eventually running them. As such, it is also responsible for
> parsing the command line arguments.
>
> The clar unit testing framework provides function `clar_test()` that
> parses command line arguments and then executes the tests for us. In
> theory that would already be sufficient. We have the special requirement
> to always generate TAP-formatted output though, so we'd have to always
> pass the "-t" argument to clar. Furthermore, some of the options exposed
> by clar are ineffective when "-t" is used, but they would still be shown
> when the user passes the "-h" parameter to have the clar show its usage.
>
> Implement our own option handling instead of using the one provided by
> clar, which gives us greater flexibility in how exactly we set things
> up.
That makes sense
> We would ideally not use any "normal" code of ours for this such that
> the unit testing framework doesn't depend on it working correctly. But
> it is somewhat dubious whether we really want to reimplement all of the
> option parsing. So for now, let's be pragmatic and reuse it until we
> find a good reason in the future why we'd really want to avoid it.
I think that's fine for now. Using parse_options() gives a much nicer
user experience than clar_test() as it supports long options and has
more flexible support for option arguments. I'd expect the code that
implements "struct string_list" and "struct strvec" to be pretty stable
so its probably safe to rely on those.
Given there's only a couple of options it wouldn't be too bad to
implement the parsing ourselves if we have to in the future. We might
need to do that to support the libification work as I suspect we wont
want to link tests for other libraries against libgit.a.
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> t/unit-tests/unit-test.c | 43 ++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 41 insertions(+), 2 deletions(-)
>
> diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
> index 3d12cde6dae..96fa64de71d 100644
> --- a/t/unit-tests/unit-test.c
> +++ b/t/unit-tests/unit-test.c
> @@ -1,6 +1,45 @@
> #include "unit-test.h"
> +#include "parse-options.h"
> +#include "string-list.h"
> +#include "strvec.h"
>
> -int cmd_main(int argc UNUSED, const char **argv UNUSED)
> +static const char * const unit_test_usage[] = {
> + N_("unit-test [<options>]"),
> + NULL,
> +};
> +
> +int cmd_main(int argc, const char **argv)
> {
> - return 0;
> + struct string_list run_args = STRING_LIST_INIT_NODUP;
> + struct string_list exclude_args = STRING_LIST_INIT_NODUP;
> + int immediate = 0;
> + struct option options[] = {
> + OPT_BOOL('i', "--immediate", &immediate,
> + N_("immediately exit upon the first failed test")),
This is unused. If we want to to behave like the "--immediate" option of
our integration tests that's hard to implement by wrapping clar_test()
which requires "-i<suite>". The simplest thing would be to just drop it
for now. Otherwise as the most likely use for "-i" is manually testing
some tests in a suite we could require "--run" with "-i". Then we would
have one or more suite names which we can append to "-i" when passing it
to clar_test(). Alternatively we could include "clar.suite" and wade
through all the test suite names ourselves to construct a suitable list
of "-i" options to pass to clar_test() but that would probably mean we
have to parse the excludes as well which makes it all a bit of a faff.
> + OPT_STRING_LIST('r', "run", &run_args, N_("name"),
> + N_("run only test suite or individual test <name>")),
It's nice that this option name now matches our integration tests. It
would be helpful to show the syntax for "name" (I think it expects
<suite>[::<test>]) but I failed to come up with a concise description to
add to the help here.
Best Wishes
Phillip
> + OPT_STRING_LIST('x', "exclude", &exclude_args, N_("name"),
> + N_("exclude test suite <name>")),
> + OPT_END(),
> + };
> + struct strvec args = STRVEC_INIT;
> + int ret;
> +
> + argc = parse_options(argc, argv, NULL, options,
> + unit_test_usage, PARSE_OPT_KEEP_ARGV0);
> + if (argc > 1)
> + usagef(_("extra command line parameter '%s'"), argv[0]);
> +
> + strvec_push(&args, argv[0]);
> + strvec_push(&args, "-t");
> + for (size_t i = 0; i < run_args.nr; i++)
> + strvec_pushf(&args, "-s%s", run_args.items[i].string);
> + for (size_t i = 0; i < exclude_args.nr; i++)
> + strvec_pushf(&args, "-x%s", exclude_args.items[i].string);
> +
> + ret = clar_test(args.nr, (char **) args.v);
> +
> + string_list_clear(&run_args, 0);
> + strvec_clear(&args);
> + return ret;
> }
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v7 11/14] t/unit-tests: implement test driver
2024-09-04 13:35 ` Phillip Wood
@ 2024-09-04 14:12 ` Patrick Steinhardt
2024-09-04 14:35 ` phillip.wood123
0 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:12 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Wed, Sep 04, 2024 at 02:35:20PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 03/09/2024 10:15, Patrick Steinhardt wrote:
> > The test driver in "unit-test.c" is responsible for setting up our unit
> > tests and eventually running them. As such, it is also responsible for
> > parsing the command line arguments.
> >
> > The clar unit testing framework provides function `clar_test()` that
> > parses command line arguments and then executes the tests for us. In
> > theory that would already be sufficient. We have the special requirement
> > to always generate TAP-formatted output though, so we'd have to always
> > pass the "-t" argument to clar. Furthermore, some of the options exposed
> > by clar are ineffective when "-t" is used, but they would still be shown
> > when the user passes the "-h" parameter to have the clar show its usage.
> >
> > Implement our own option handling instead of using the one provided by
> > clar, which gives us greater flexibility in how exactly we set things
> > up.
>
> That makes sense
>
> > We would ideally not use any "normal" code of ours for this such that
> > the unit testing framework doesn't depend on it working correctly. But
> > it is somewhat dubious whether we really want to reimplement all of the
> > option parsing. So for now, let's be pragmatic and reuse it until we
> > find a good reason in the future why we'd really want to avoid it.
>
> I think that's fine for now. Using parse_options() gives a much nicer user
> experience than clar_test() as it supports long options and has more
> flexible support for option arguments. I'd expect the code that implements
> "struct string_list" and "struct strvec" to be pretty stable so its probably
> safe to rely on those.
>
> Given there's only a couple of options it wouldn't be too bad to implement
> the parsing ourselves if we have to in the future. We might need to do that
> to support the libification work as I suspect we wont want to link tests for
> other libraries against libgit.a.
>
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> > t/unit-tests/unit-test.c | 43 ++++++++++++++++++++++++++++++++++++++--
> > 1 file changed, 41 insertions(+), 2 deletions(-)
> >
> > diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
> > index 3d12cde6dae..96fa64de71d 100644
> > --- a/t/unit-tests/unit-test.c
> > +++ b/t/unit-tests/unit-test.c
> > @@ -1,6 +1,45 @@
> > #include "unit-test.h"
> > +#include "parse-options.h"
> > +#include "string-list.h"
> > +#include "strvec.h"
> > -int cmd_main(int argc UNUSED, const char **argv UNUSED)
> > +static const char * const unit_test_usage[] = {
> > + N_("unit-test [<options>]"),
> > + NULL,
> > +};
> > +
> > +int cmd_main(int argc, const char **argv)
> > {
> > - return 0;
> > + struct string_list run_args = STRING_LIST_INIT_NODUP;
> > + struct string_list exclude_args = STRING_LIST_INIT_NODUP;
> > + int immediate = 0;
> > + struct option options[] = {
> > + OPT_BOOL('i', "--immediate", &immediate,
> > + N_("immediately exit upon the first failed test")),
>
> This is unused. If we want to to behave like the "--immediate" option of our
> integration tests that's hard to implement by wrapping clar_test() which
> requires "-i<suite>". The simplest thing would be to just drop it for now.
> Otherwise as the most likely use for "-i" is manually testing some tests in
> a suite we could require "--run" with "-i". Then we would have one or more
> suite names which we can append to "-i" when passing it to clar_test().
> Alternatively we could include "clar.suite" and wade through all the test
> suite names ourselves to construct a suitable list of "-i" options to pass
> to clar_test() but that would probably mean we have to parse the excludes as
> well which makes it all a bit of a faff.
Oh, that's a plain oversight on my side. It is easy to wire up given
that the clar already supports it via "-Q". Also made me notice that I
wrote "--immediate" instead of "immediate".
> > + OPT_STRING_LIST('r', "run", &run_args, N_("name"),
> > + N_("run only test suite or individual test <name>")),
>
> It's nice that this option name now matches our integration tests. It would
> be helpful to show the syntax for "name" (I think it expects
> <suite>[::<test>]) but I failed to come up with a concise description to add
> to the help here.
Isn't `<suite[::test]>` concise enough? I certainly like it.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v7 11/14] t/unit-tests: implement test driver
2024-09-04 14:12 ` Patrick Steinhardt
@ 2024-09-04 14:35 ` phillip.wood123
0 siblings, 0 replies; 172+ messages in thread
From: phillip.wood123 @ 2024-09-04 14:35 UTC (permalink / raw)
To: Patrick Steinhardt, phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 04/09/2024 15:12, Patrick Steinhardt wrote:
> On Wed, Sep 04, 2024 at 02:35:20PM +0100, Phillip Wood wrote:
> Oh, that's a plain oversight on my side. It is easy to wire up given
> that the clar already supports it via "-Q". Also made me notice that I
> wrote "--immediate" instead of "immediate".
That's handy, I'd missed clar's "-Q" option.
>>> + OPT_STRING_LIST('r', "run", &run_args, N_("name"),
>>> + N_("run only test suite or individual test <name>")),
>>
>> It's nice that this option name now matches our integration tests. It would
>> be helpful to show the syntax for "name" (I think it expects
>> <suite>[::<test>]) but I failed to come up with a concise description to add
>> to the help here.
>
> Isn't `<suite[::test]>` concise enough? I certainly like it.
Sounds good
Phillip
> Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v7 12/14] t/unit-tests: convert strvec tests to use clar
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (10 preceding siblings ...)
2024-09-03 9:15 ` [PATCH v7 11/14] t/unit-tests: implement test driver Patrick Steinhardt
@ 2024-09-03 9:15 ` Patrick Steinhardt
2024-09-03 9:15 ` [PATCH v7 13/14] t/unit-tests: convert ctype " Patrick Steinhardt
` (2 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary with an injected error, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
not ok 4 - strvec::push
---
reason: |
String mismatch: (&vec)->v[i] != expect[i]
'foo' != 'fo' (at byte 2)
at:
file: 't/unit-tests/strvec.c'
line: 48
function: 'test_strvec__push'
---
ok 5 - strvec::pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/strvec.c | 241 ++++++++++++++++++++++++++++++++++++++++
t/unit-tests/t-strvec.c | 211 -----------------------------------
3 files changed, 242 insertions(+), 212 deletions(-)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
diff --git a/Makefile b/Makefile
index e38146b5eb0..56ce6c00e44 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1356,7 +1357,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-reftable-tree
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/strvec.c b/t/unit-tests/strvec.c
new file mode 100644
index 00000000000..bf4c0cb172e
--- /dev/null
+++ b/t/unit-tests/strvec.c
@@ -0,0 +1,241 @@
+#include "unit-test.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define check_strvec(vec, ...) \
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
+ size_t expect_len = ARRAY_SIZE(expect); \
+ cl_assert(expect_len > 0); \
+ cl_assert_equal_p(expect[expect_len - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, expect_len - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < expect_len; i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
+
+void test_strvec__init(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__dynamic_init(void)
+{
+ struct strvec vec;
+
+ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__clear(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_push(&vec, "foo");
+ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__push(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_push(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+
+ strvec_push(&vec, "bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushf(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushf(&vec, "foo: %d", 1);
+ check_strvec(&vec, "foo: 1", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushl(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushv(void)
+{
+ const char *strings[] = {
+ "foo", "bar", "baz", NULL,
+ };
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushv(&vec, strings);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 0, "replaced");
+ check_strvec(&vec, "replaced", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 2, "replaced");
+ check_strvec(&vec, "foo", "bar", "replaced", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 1, "replaced");
+ check_strvec(&vec, "foo", "replaced", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_with_substring(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", NULL);
+ strvec_replace(&vec, 0, vec.v[0] + 1);
+ check_strvec(&vec, "oo", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 0);
+ check_strvec(&vec, "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 2);
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 1);
+ check_strvec(&vec, "foo", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pop_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pop(&vec);
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pop_non_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_pop(&vec);
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_empty_string(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_single_item(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_multiple_items(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "foo bar baz");
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_whitespace_only(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, " \t\n");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_multiple_consecutive_whitespaces(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "foo\n\t bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__detach(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ const char **detached;
+
+ strvec_push(&vec, "foo");
+
+ detached = strvec_detach(&vec);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
+
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+
+ free((char *) detached[0]);
+ free(detached);
+}
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
deleted file mode 100644
index c4bac8fc91b..00000000000
--- a/t/unit-tests/t-strvec.c
+++ /dev/null
@@ -1,211 +0,0 @@
-#include "test-lib.h"
-#include "strbuf.h"
-#include "strvec.h"
-
-#define check_strvec(vec, ...) \
- do { \
- const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
- } while (0)
-
-int cmd_main(int argc, const char **argv)
-{
- if_test ("static initialization") {
- struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("dynamic initialization") {
- struct strvec vec;
- strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("clear") {
- struct strvec vec = STRVEC_INIT;
- strvec_push(&vec, "foo");
- strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("push") {
- struct strvec vec = STRVEC_INIT;
-
- strvec_push(&vec, "foo");
- check_strvec(&vec, "foo", NULL);
-
- strvec_push(&vec, "bar");
- check_strvec(&vec, "foo", "bar", NULL);
-
- strvec_clear(&vec);
- }
-
- if_test ("pushf") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushf(&vec, "foo: %d", 1);
- check_strvec(&vec, "foo: 1", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pushl") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- check_strvec(&vec, "foo", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pushv") {
- const char *strings[] = {
- "foo", "bar", "baz", NULL,
- };
- struct strvec vec = STRVEC_INIT;
-
- strvec_pushv(&vec, strings);
- check_strvec(&vec, "foo", "bar", "baz", NULL);
-
- strvec_clear(&vec);
- }
-
- if_test ("replace at head") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 0, "replaced");
- check_strvec(&vec, "replaced", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace at tail") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 2, "replaced");
- check_strvec(&vec, "foo", "bar", "replaced", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace in between") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 1, "replaced");
- check_strvec(&vec, "foo", "replaced", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace with substring") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", NULL);
- strvec_replace(&vec, 0, vec.v[0] + 1);
- check_strvec(&vec, "oo", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove at head") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 0);
- check_strvec(&vec, "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove at tail") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 2);
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove in between") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 1);
- check_strvec(&vec, "foo", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pop with empty array") {
- struct strvec vec = STRVEC_INIT;
- strvec_pop(&vec);
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pop with non-empty array") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_pop(&vec);
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split empty string") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "");
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split single item") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo");
- check_strvec(&vec, "foo", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split multiple items") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo bar baz");
- check_strvec(&vec, "foo", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split whitespace only") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, " \t\n");
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split multiple consecutive whitespaces") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo\n\t bar");
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("detach") {
- struct strvec vec = STRVEC_INIT;
- const char **detached;
-
- strvec_push(&vec, "foo");
-
- detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
-
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
-
- free((char *) detached[0]);
- free(detached);
- }
-
- return test_done();
-}
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 13/14] t/unit-tests: convert ctype tests to use clar
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (11 preceding siblings ...)
2024-09-03 9:15 ` [PATCH v7 12/14] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-09-03 9:15 ` Patrick Steinhardt
2024-09-03 9:15 ` [PATCH v7 14/14] clar: add CMake support Patrick Steinhardt
2024-09-04 13:35 ` [PATCH v7 00/14] Introduce clar testing framework Phillip Wood
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Convert the ctype tests to use the new clar unit testing framework.
Introduce a new function `cl_failf()` that allows us to print a
formatted error message, which we can use to point out which of the
characters was classified incorrectly. This results in output like this
on failure:
# start of suite 1: ctype
not ok 1 - ctype::isspace
---
reason: |
Test failed.
0x0d is classified incorrectly: expected 0, got 1
at:
file: 't/unit-tests/ctype.c'
line: 36
function: 'test_ctype__isspace'
---
ok 2 - ctype::isdigit
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
ok 6 - ctype::is_regex_special
ok 7 - ctype::is_pathspec_magic
ok 8 - ctype::isascii
ok 9 - ctype::islower
ok 10 - ctype::isupper
ok 11 - ctype::iscntrl
ok 12 - ctype::ispunct
ok 13 - ctype::isxdigit
ok 14 - ctype::isprint
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++++----
t/unit-tests/unit-test.h | 7 +++
3 files changed, 69 insertions(+), 11 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
diff --git a/Makefile b/Makefile
index 56ce6c00e44..c841cf70063 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-hashmap
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 68%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index e28a7f50f9a..32e65867cdc 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,16 +1,16 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- if_test (#class " works") { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
+ for (int i = 0; i < 256; i++) { \
+ int actual = class(i), expect = !!memchr(string, i, len); \
+ if (actual != expect) \
+ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \
+ i, expect, actual); \
} \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -31,21 +31,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
index 66ec2387cc6..85e5d6a948a 100644
--- a/t/unit-tests/unit-test.h
+++ b/t/unit-tests/unit-test.h
@@ -1,3 +1,10 @@
#include "git-compat-util.h"
#include "clar/clar.h"
#include "clar-decls.h"
+#include "strbuf.h"
+
+#define cl_failf(fmt, ...) do { \
+ char desc[4096]; \
+ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
+ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
+} while (0)
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v7 14/14] clar: add CMake support
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (12 preceding siblings ...)
2024-09-03 9:15 ` [PATCH v7 13/14] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-09-03 9:15 ` Patrick Steinhardt
2024-09-04 13:35 ` [PATCH v7 00/14] Introduce clar testing framework Phillip Wood
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-03 9:15 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Now that we're using `clar` as powerful test framework, we have to
adjust the Visual C build (read: the CMake definition) to be able to
handle that, too.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 832f46b316b..608fd3fe709 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -1004,6 +1004,59 @@ foreach(unit_test ${unit_test_PROGRAMS})
endif()
endforeach()
+parse_makefile_for_scripts(unit_tests_SUITES "UNIT_TESTS_SUITES" "")
+
+set(clar_decls "")
+set(clar_cbs "")
+set(clar_cbs_count 0)
+set(clar_suites "static struct clar_suite _clar_suites[] = {\n")
+list(LENGTH unit_tests_SUITES clar_suites_count)
+foreach(suite ${unit_tests_SUITES})
+ file(STRINGS "${CMAKE_SOURCE_DIR}/t/unit-tests/${suite}.c" decls
+ REGEX "^void test_${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*\\(void\\)$")
+
+ list(LENGTH decls decls_count)
+ string(REGEX REPLACE "void (test_${suite}__([a-zA-Z_0-9]*))\\(void\\)" " { \"\\2\", &\\1 },\n" cbs ${decls})
+ string(JOIN "" cbs ${cbs})
+ list(TRANSFORM decls PREPEND "extern ")
+ string(JOIN ";\n" decls ${decls})
+
+ string(APPEND clar_decls "${decls};\n")
+ string(APPEND clar_cbs
+ "static const struct clar_func _clar_cb_${suite}[] = {\n"
+ ${cbs}
+ "};\n")
+ string(APPEND clar_suites
+ " {\n"
+ " \"${suite}\",\n"
+ " { NULL, NULL },\n"
+ " { NULL, NULL },\n"
+ " _clar_cb_${suite}, ${decls_count}, 1\n"
+ " },\n")
+ math(EXPR clar_cbs_count "${clar_cbs_count}+${decls_count}")
+endforeach()
+string(APPEND clar_suites
+ "};\n"
+ "static const size_t _clar_suite_count = ${clar_suites_count};\n"
+ "static const size_t _clar_callback_count = ${clar_cbs_count};\n")
+file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h" "${clar_decls}")
+file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite" "${clar_decls}" "${clar_cbs}" "${clar_suites}")
+
+list(TRANSFORM unit_tests_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/")
+list(TRANSFORM unit_tests_SUITES APPEND ".c")
+add_library(unit-tests-lib ${unit_tests_SUITES} "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c")
+target_include_directories(unit-tests-lib PRIVATE "${CMAKE_SOURCE_DIR}/t/unit-tests")
+add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c")
+target_link_libraries(unit-tests unit-tests-lib common-main)
+set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+if(MSVC)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+endif()
+
#test-tool
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
--
2.46.0.421.g159f2d50e7.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v7 00/14] Introduce clar testing framework
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
` (13 preceding siblings ...)
2024-09-03 9:15 ` [PATCH v7 14/14] clar: add CMake support Patrick Steinhardt
@ 2024-09-04 13:35 ` Phillip Wood
2024-09-04 14:12 ` Patrick Steinhardt
14 siblings, 1 reply; 172+ messages in thread
From: Phillip Wood @ 2024-09-04 13:35 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 03/09/2024 10:14, Patrick Steinhardt wrote:
> Hi,
>
> this is the seventh version fo my patch series that introduces the clar
> unit testing framework.
>
> Changes compared to v6:
>
> - Add some explanations for why we introduce clar in the first place
> to the second commit message.
>
> - Split out implementation of the test driver, that is the main
> function, into a separate commit.
>
> - Reimplement argument parsing ourselves. This allows more flexibility
> and gets rid of some options that do not make sense for us.
>
> - Add an empty line between declarations and code.
>
> - Improve the test messages in ctype tests to also mention our
> expectations.
>
> - Adapt `cl_failf()` to not use `xstrfmt()`, but `snprintf()` with a
> static buffer instead.
I've left some comments on patch 11 which is new. Apart from that
range-diff looks fine to me.
Thanks for working on this
Phillip
> Thanks!
>
> Patrick
>
> Johannes Schindelin (4):
> clar: avoid compile error with mingw-w64
> clar(win32): avoid compile error due to unused `fs_copy()`
> clar: stop including `shellapi.h` unnecessarily
> clar: add CMake support
>
> Patrick Steinhardt (10):
> t: do not pass GIT_TEST_OPTS to unit tests with prove
> t: import the clar unit testing framework
> t/clar: fix compatibility with NonStop
> Makefile: fix sparse dependency on GENERATED_H
> Makefile: make hdr-check depend on generated headers
> Makefile: do not use sparse on third-party sources
> Makefile: wire up the clar unit testing framework
> t/unit-tests: implement test driver
> t/unit-tests: convert strvec tests to use clar
> t/unit-tests: convert ctype tests to use clar
>
> .gitignore | 1 +
> Documentation/technical/unit-tests.txt | 2 +
> Makefile | 53 +-
> contrib/buildsystems/CMakeLists.txt | 53 ++
> t/Makefile | 4 +-
> t/run-test.sh | 2 +-
> t/unit-tests/.gitignore | 2 +
> t/unit-tests/clar-generate.awk | 50 ++
> t/unit-tests/clar/.github/workflows/ci.yml | 23 +
> t/unit-tests/clar/COPYING | 15 +
> t/unit-tests/clar/README.md | 329 ++++++++
> t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
> t/unit-tests/clar/clar.h | 173 +++++
> t/unit-tests/clar/clar/fixtures.h | 50 ++
> t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
> t/unit-tests/clar/clar/print.h | 211 ++++++
> t/unit-tests/clar/clar/sandbox.h | 159 ++++
> t/unit-tests/clar/clar/summary.h | 143 ++++
> t/unit-tests/clar/generate.py | 266 +++++++
> t/unit-tests/clar/test/.gitignore | 4 +
> t/unit-tests/clar/test/Makefile | 39 +
> t/unit-tests/clar/test/clar_test.h | 16 +
> t/unit-tests/clar/test/main.c | 40 +
> t/unit-tests/clar/test/main.c.sample | 27 +
> t/unit-tests/clar/test/resources/test/file | 1 +
> t/unit-tests/clar/test/sample.c | 84 ++
> t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
> t/unit-tests/strvec.c | 241 ++++++
> t/unit-tests/t-strvec.c | 211 ------
> t/unit-tests/unit-test.c | 45 ++
> t/unit-tests/unit-test.h | 10 +
> 31 files changed, 3457 insertions(+), 234 deletions(-)
> create mode 100644 t/unit-tests/clar-generate.awk
> create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
> create mode 100644 t/unit-tests/clar/COPYING
> create mode 100644 t/unit-tests/clar/README.md
> create mode 100644 t/unit-tests/clar/clar.c
> create mode 100644 t/unit-tests/clar/clar.h
> create mode 100644 t/unit-tests/clar/clar/fixtures.h
> create mode 100644 t/unit-tests/clar/clar/fs.h
> create mode 100644 t/unit-tests/clar/clar/print.h
> create mode 100644 t/unit-tests/clar/clar/sandbox.h
> create mode 100644 t/unit-tests/clar/clar/summary.h
> create mode 100755 t/unit-tests/clar/generate.py
> create mode 100644 t/unit-tests/clar/test/.gitignore
> create mode 100644 t/unit-tests/clar/test/Makefile
> create mode 100644 t/unit-tests/clar/test/clar_test.h
> create mode 100644 t/unit-tests/clar/test/main.c
> create mode 100644 t/unit-tests/clar/test/main.c.sample
> create mode 100644 t/unit-tests/clar/test/resources/test/file
> create mode 100644 t/unit-tests/clar/test/sample.c
> rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
> create mode 100644 t/unit-tests/strvec.c
> delete mode 100644 t/unit-tests/t-strvec.c
> create mode 100644 t/unit-tests/unit-test.c
> create mode 100644 t/unit-tests/unit-test.h
>
> Range-diff against v6:
> 1: e48a6461137 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove
> 2: 1710e9f9ff7 ! 2: 55a9b46e65f t: import the clar unit testing framework
> @@ Metadata
> ## Commit message ##
> t: import the clar unit testing framework
>
> + Our unit testing framework is a homegrown solution. While it supports
> + most of our needs, it is likely that the volume of unit tests will grow
> + quite a bit in the future such that we can exercise low-level subsystems
> + directly. This surfaces several shortcomings that the current solution
> + has:
> +
> + - There is no way to run only one specific tests. While some of our
> + unit tests wire this up manually, others don't. In general, it
> + requires quite a bit of boilerplate to get this set up correctly.
> +
> + - Failures do not cause a test to stop execution directly. Instead,
> + the test author needs to return manually whenever an assertion
> + fails. This is rather verbose and is not done correctly in most of
> + our unit tests.
> +
> + - Wiring up a new testcase requires both implementing the test
> + function and calling it in the respective test suite's main
> + function, which is creating code duplication.
> +
> + We can of course fix all of these issues ourselves, but that feels
> + rather pointless when there are already so many unit testing frameworks
> + out there that have those features.
> +
> + We line out some requirements for any unit testing framework in
> + "Documentation/technical/unit-tests.txt". The "clar" unit testing
> + framework, which isn't listed in that table yet, ticks many of the
> + boxes:
> +
> + - It is licensed under ISC, which is compatible.
> +
> + - It is easily vendorable because it is rather tiny at around 1200
> + lines of code.
> +
> + - It is easily hackable due to the same reason.
> +
> + - It has TAP support.
> +
> + - It has skippable tests.
> +
> + - It preprocesses test files in order to extract test functions, which
> + then get wired up automatically.
> +
> + While it's not perfect, the fact that clar originates from the libgit2
> + project means that it should be rather easy for us to collaborate with
> + upstream to plug any gaps.
> +
> Import the clar unit testing framework at commit 1516124 (Merge pull
> request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
> will be wired up in subsequent commits.
> 3: 5c21aa87aa2 = 3: f24401f0a87 t/clar: fix compatibility with NonStop
> 4: 06d2bce0d82 = 4: 658a601c541 clar: avoid compile error with mingw-w64
> 5: f88b3421a09 = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()`
> 6: 5fb4c55be33 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily
> 7: e0dcbd5ca83 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H
> 8: 77a03f8df70 = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers
> 9: c91dd7327e3 = 9: 7d0f494850a Makefile: do not use sparse on third-party sources
> 10: 115c15aa9ae ! 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework
> @@ Commit message
> anything. Thus, it would cause a compiler error if a function name was
> mistyped and thus not picked up by "generate.py".
>
> + The test driver "unit-test.c" is an empty stub for now. It will get
> + implemented in the next commit.
> +
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>
> ## .gitignore ##
> @@ t/unit-tests/unit-test.c (new)
> @@
> +#include "unit-test.h"
> +
> -+int cmd_main(int argc, const char **argv)
> ++int cmd_main(int argc UNUSED, const char **argv UNUSED)
> +{
> -+ const char **argv_copy;
> -+ int ret;
> -+
> -+ /* Append the "-t" flag such that the tests generate TAP output. */
> -+ ALLOC_ARRAY(argv_copy, argc + 2);
> -+ COPY_ARRAY(argv_copy, argv, argc);
> -+ argv_copy[argc++] = "-t";
> -+ argv_copy[argc] = NULL;
> -+
> -+ ret = clar_test(argc, (char **) argv_copy);
> -+
> -+ free(argv_copy);
> -+ return ret;
> ++ return 0;
> +}
>
> ## t/unit-tests/unit-test.h (new) ##
> -: ----------- > 11: 8bd5b3e2b29 t/unit-tests: implement test driver
> 11: b3b8df04872 ! 12: 3c3b9eacdfb t/unit-tests: convert strvec tests to use clar
> @@ Commit message
> clar-based tests looks like.
>
> The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
> - running that binary, it generates TAP output:
> + running that binary with an injected error, it generates TAP output:
>
> # ./t/unit-tests/bin/unit-tests
> TAP version 13
> @@ Commit message
> ok 1 - strvec::init
> ok 2 - strvec::dynamic_init
> ok 3 - strvec::clear
> - ok 4 - strvec::push
> - ok 5 - strvec::pushft_pushf
> + not ok 4 - strvec::push
> + ---
> + reason: |
> + String mismatch: (&vec)->v[i] != expect[i]
> + 'foo' != 'fo' (at byte 2)
> + at:
> + file: 't/unit-tests/strvec.c'
> + line: 48
> + function: 'test_strvec__push'
> + ---
> + ok 5 - strvec::pushf
> ok 6 - strvec::pushl
> ok 7 - strvec::pushv
> ok 8 - strvec::replace_at_head
> @@ t/unit-tests/strvec.c (new)
> +#define check_strvec(vec, ...) \
> + do { \
> + const char *expect[] = { __VA_ARGS__ }; \
> -+ cl_assert(ARRAY_SIZE(expect) > 0); \
> -+ cl_assert_equal_p(expect[ARRAY_SIZE(expect) - 1], NULL); \
> -+ cl_assert_equal_i((vec)->nr, ARRAY_SIZE(expect) - 1); \
> ++ size_t expect_len = ARRAY_SIZE(expect); \
> ++ cl_assert(expect_len > 0); \
> ++ cl_assert_equal_p(expect[expect_len - 1], NULL); \
> ++ cl_assert_equal_i((vec)->nr, expect_len - 1); \
> + cl_assert((vec)->nr <= (vec)->alloc); \
> -+ for (size_t i = 0; i < ARRAY_SIZE(expect); i++) \
> ++ for (size_t i = 0; i < expect_len; i++) \
> + cl_assert_equal_s((vec)->v[i], expect[i]); \
> + } while (0)
> +
> +void test_strvec__init(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> + cl_assert_equal_i(vec.alloc, 0);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__dynamic_init(void)
> +{
> + struct strvec vec;
> ++
> + strvec_init(&vec);
> + cl_assert_equal_p(vec.v, empty_strvec);
> + cl_assert_equal_i(vec.nr, 0);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__clear(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_push(&vec, "foo");
> + strvec_clear(&vec);
> + cl_assert_equal_p(vec.v, empty_strvec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__pushf(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushf(&vec, "foo: %d", 1);
> + check_strvec(&vec, "foo: 1", NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__pushl(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + check_strvec(&vec, "foo", "bar", "baz", NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__replace_at_head(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_replace(&vec, 0, "replaced");
> + check_strvec(&vec, "replaced", "bar", "baz", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__replace_in_between(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_replace(&vec, 1, "replaced");
> + check_strvec(&vec, "foo", "replaced", "baz", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__replace_with_substring(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", NULL);
> + strvec_replace(&vec, 0, vec.v[0] + 1);
> + check_strvec(&vec, "oo", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__remove_at_head(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_remove(&vec, 0);
> + check_strvec(&vec, "bar", "baz", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__remove_at_tail(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_remove(&vec, 2);
> + check_strvec(&vec, "foo", "bar", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__remove_in_between(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_remove(&vec, 1);
> + check_strvec(&vec, "foo", "baz", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__pop_empty_array(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pop(&vec);
> + check_strvec(&vec, NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__pop_non_empty_array(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_pushl(&vec, "foo", "bar", "baz", NULL);
> + strvec_pop(&vec);
> + check_strvec(&vec, "foo", "bar", NULL);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__split_empty_string(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_split(&vec, "");
> + check_strvec(&vec, NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__split_single_item(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_split(&vec, "foo");
> + check_strvec(&vec, "foo", NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__split_multiple_items(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_split(&vec, "foo bar baz");
> + check_strvec(&vec, "foo", "bar", "baz", NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__split_whitespace_only(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_split(&vec, " \t\n");
> + check_strvec(&vec, NULL);
> + strvec_clear(&vec);
> @@ t/unit-tests/strvec.c (new)
> +void test_strvec__split_multiple_consecutive_whitespaces(void)
> +{
> + struct strvec vec = STRVEC_INIT;
> ++
> + strvec_split(&vec, "foo\n\t bar");
> + check_strvec(&vec, "foo", "bar", NULL);
> + strvec_clear(&vec);
> 12: 1ac2e48a7f2 ! 13: c8360db2f86 t/unit-tests: convert ctype tests to use clar
> @@ Commit message
> on failure:
>
> # start of suite 1: ctype
> - ok 1 - ctype::isspace
> - not ok 2 - ctype::isdigit
> + not ok 1 - ctype::isspace
> ---
> reason: |
> Test failed.
> - 0x61 is classified incorrectly
> + 0x0d is classified incorrectly: expected 0, got 1
> at:
> file: 't/unit-tests/ctype.c'
> - line: 38
> - function: 'test_ctype__isdigit'
> + line: 36
> + function: 'test_ctype__isspace'
> ---
> + ok 2 - ctype::isdigit
> ok 3 - ctype::isalpha
> ok 4 - ctype::isalnum
> ok 5 - ctype::is_glob_special
> @@ t/unit-tests/t-ctype.c => t/unit-tests/ctype.c
> - test_msg(" i: 0x%02x", i); \
> - } \
> - check(!class(EOF)); \
> -- } \
> -+ for (int i = 0; i < 256; i++) \
> -+ if (class(i) != !!memchr(string, i, len)) \
> -+ cl_failf("0x%02x is classified incorrectly", i); \
> ++ for (int i = 0; i < 256; i++) { \
> ++ int actual = class(i), expect = !!memchr(string, i, len); \
> ++ if (actual != expect) \
> ++ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \
> ++ i, expect, actual); \
> + } \
> + cl_assert(!class(EOF)); \
> } while (0)
>
> @@ t/unit-tests/unit-test.h
> +#include "strbuf.h"
> +
> +#define cl_failf(fmt, ...) do { \
> -+ char *desc = xstrfmt(fmt, __VA_ARGS__); \
> ++ char desc[4096]; \
> ++ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
> + clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
> -+ free(desc); \
> +} while (0)
> 13: 131036c398e = 14: d51c146cd9d clar: add CMake support
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v7 00/14] Introduce clar testing framework
2024-09-04 13:35 ` [PATCH v7 00/14] Introduce clar testing framework Phillip Wood
@ 2024-09-04 14:12 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:12 UTC (permalink / raw)
To: phillip.wood
Cc: git, René Scharfe, Junio C Hamano, Kyle Lippincott,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Wed, Sep 04, 2024 at 02:35:33PM +0100, Phillip Wood wrote:
> Hi Patrick
>
> On 03/09/2024 10:14, Patrick Steinhardt wrote:
> > Hi,
> >
> > this is the seventh version fo my patch series that introduces the clar
> > unit testing framework.
> >
> > Changes compared to v6:
> >
> > - Add some explanations for why we introduce clar in the first place
> > to the second commit message.
> >
> > - Split out implementation of the test driver, that is the main
> > function, into a separate commit.
> >
> > - Reimplement argument parsing ourselves. This allows more flexibility
> > and gets rid of some options that do not make sense for us.
> >
> > - Add an empty line between declarations and code.
> >
> > - Improve the test messages in ctype tests to also mention our
> > expectations.
> >
> > - Adapt `cl_failf()` to not use `xstrfmt()`, but `snprintf()` with a
> > static buffer instead.
>
> I've left some comments on patch 11 which is new. Apart from that range-diff
> looks fine to me.
>
> Thanks for working on this
>
> Phillip
Thanks for all your reviews on this series, highly appreciated!
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v8 00/14] Introduce clar testing framework
2024-07-31 9:04 [RFC PATCH 0/3] Introduce clar testing framework Patrick Steinhardt
` (10 preceding siblings ...)
2024-09-03 9:14 ` [PATCH v7 00/14] " Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
` (14 more replies)
11 siblings, 15 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi,
this is the 8th version of my patch series that introduces the clar
testing framework.
Changes compared to v7:
- Properly wire up the "--immediate" flag.
- Give a hint for the syntax to run only specific suites or tests for
the "--run" and "--exclude" options.
Thanks!
Patrick
Johannes Schindelin (4):
clar: avoid compile error with mingw-w64
clar(win32): avoid compile error due to unused `fs_copy()`
clar: stop including `shellapi.h` unnecessarily
clar: add CMake support
Patrick Steinhardt (10):
t: do not pass GIT_TEST_OPTS to unit tests with prove
t: import the clar unit testing framework
t/clar: fix compatibility with NonStop
Makefile: fix sparse dependency on GENERATED_H
Makefile: make hdr-check depend on generated headers
Makefile: do not use sparse on third-party sources
Makefile: wire up the clar unit testing framework
t/unit-tests: implement test driver
t/unit-tests: convert strvec tests to use clar
t/unit-tests: convert ctype tests to use clar
.gitignore | 1 +
Documentation/technical/unit-tests.txt | 2 +
Makefile | 53 +-
contrib/buildsystems/CMakeLists.txt | 53 ++
t/Makefile | 4 +-
t/run-test.sh | 2 +-
t/unit-tests/.gitignore | 2 +
t/unit-tests/clar-generate.awk | 50 ++
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 159 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
t/unit-tests/strvec.c | 241 ++++++
t/unit-tests/t-strvec.c | 211 ------
t/unit-tests/unit-test.c | 47 ++
t/unit-tests/unit-test.h | 10 +
31 files changed, 3459 insertions(+), 234 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
Range-diff against v7:
1: b67f10ec0b0 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove
2: 55a9b46e65f = 2: 55a9b46e65f t: import the clar unit testing framework
3: f24401f0a87 = 3: f24401f0a87 t/clar: fix compatibility with NonStop
4: 658a601c541 = 4: 658a601c541 clar: avoid compile error with mingw-w64
5: 0b8a6ac5fed = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()`
6: c50e7a0ea68 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily
7: b8f3f16dd27 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H
8: 3d3fe443b9a = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers
9: 7d0f494850a = 9: 7d0f494850a Makefile: do not use sparse on third-party sources
10: 9c74c5ae019 = 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework
11: 8bd5b3e2b29 ! 11: 81d932bfa33 t/unit-tests: implement test driver
@@ t/unit-tests/unit-test.c
+ struct string_list exclude_args = STRING_LIST_INIT_NODUP;
+ int immediate = 0;
+ struct option options[] = {
-+ OPT_BOOL('i', "--immediate", &immediate,
++ OPT_BOOL('i', "immediate", &immediate,
+ N_("immediately exit upon the first failed test")),
-+ OPT_STRING_LIST('r', "run", &run_args, N_("name"),
-+ N_("run only test suite or individual test <name>")),
-+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("name"),
-+ N_("exclude test suite <name>")),
++ OPT_STRING_LIST('r', "run", &run_args, N_("suite[::test]"),
++ N_("run only test suite or individual test <suite[::test]>")),
++ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("suite"),
++ N_("exclude test suite <suite>")),
+ OPT_END(),
+ };
+ struct strvec args = STRVEC_INIT;
@@ t/unit-tests/unit-test.c
+
+ strvec_push(&args, argv[0]);
+ strvec_push(&args, "-t");
++ if (immediate)
++ strvec_push(&args, "-Q");
+ for (size_t i = 0; i < run_args.nr; i++)
+ strvec_pushf(&args, "-s%s", run_args.items[i].string);
+ for (size_t i = 0; i < exclude_args.nr; i++)
12: 3c3b9eacdfb = 12: 604303e31aa t/unit-tests: convert strvec tests to use clar
13: c8360db2f86 = 13: ba05b9f1eef t/unit-tests: convert ctype tests to use clar
14: d51c146cd9d = 14: 8441d29daa8 clar: add CMake support
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v8 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 02/14] t: import the clar unit testing framework Patrick Steinhardt
` (13 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
When using the prove target, we append GIT_TEST_OPTS to the arguments
that we execute each of the tests with. This doesn't only include the
intended test scripts, but also ends up passing the arguments to our
unit tests. This is unintentional though as they do not even know to
interpret those arguments, and is inconsistent with how we execute unit
tests without prove.
This isn't much of an issue because our current set of unit tests mostly
ignore their arguments anyway. With the introduction of clar-based unit
tests this is about to become an issue though, as these do parse their
command line argument to alter behaviour.
Prepare for this by passing GIT_TEST_OPTS to "run-test.sh" via an
environment variable. Like this, we can conditionally forward it to our
test scripts, only.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/Makefile | 3 ++-
t/run-test.sh | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/t/Makefile b/t/Makefile
index 4c30e7c06fb..d2212de0b78 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -68,7 +68,8 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"
+ @$(CHAINLINTSUPPRESS) TEST_OPTIONS='$(GIT_TEST_OPTS)' TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS)
$(MAKE) clean-except-prove-cache
$(T):
diff --git a/t/run-test.sh b/t/run-test.sh
index 13c353b91b4..63328ac630c 100755
--- a/t/run-test.sh
+++ b/t/run-test.sh
@@ -10,7 +10,7 @@ case "$1" in
echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
exit 1
fi
- exec "${TEST_SHELL_PATH}" "$@"
+ exec "${TEST_SHELL_PATH}" "$@" ${TEST_OPTIONS}
;;
*)
exec "$@"
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 02/14] t: import the clar unit testing framework
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 03/14] t/clar: fix compatibility with NonStop Patrick Steinhardt
` (12 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Our unit testing framework is a homegrown solution. While it supports
most of our needs, it is likely that the volume of unit tests will grow
quite a bit in the future such that we can exercise low-level subsystems
directly. This surfaces several shortcomings that the current solution
has:
- There is no way to run only one specific tests. While some of our
unit tests wire this up manually, others don't. In general, it
requires quite a bit of boilerplate to get this set up correctly.
- Failures do not cause a test to stop execution directly. Instead,
the test author needs to return manually whenever an assertion
fails. This is rather verbose and is not done correctly in most of
our unit tests.
- Wiring up a new testcase requires both implementing the test
function and calling it in the respective test suite's main
function, which is creating code duplication.
We can of course fix all of these issues ourselves, but that feels
rather pointless when there are already so many unit testing frameworks
out there that have those features.
We line out some requirements for any unit testing framework in
"Documentation/technical/unit-tests.txt". The "clar" unit testing
framework, which isn't listed in that table yet, ticks many of the
boxes:
- It is licensed under ISC, which is compatible.
- It is easily vendorable because it is rather tiny at around 1200
lines of code.
- It is easily hackable due to the same reason.
- It has TAP support.
- It has skippable tests.
- It preprocesses test files in order to extract test functions, which
then get wired up automatically.
While it's not perfect, the fact that clar originates from the libgit2
project means that it should be rather easy for us to collaborate with
upstream to plug any gaps.
Import the clar unit testing framework at commit 1516124 (Merge pull
request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework
will be wired up in subsequent commits.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Documentation/technical/unit-tests.txt | 2 +
Makefile | 4 +-
t/unit-tests/clar/.github/workflows/ci.yml | 23 +
t/unit-tests/clar/COPYING | 15 +
t/unit-tests/clar/README.md | 329 ++++++++
t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
t/unit-tests/clar/clar.h | 173 +++++
t/unit-tests/clar/clar/fixtures.h | 50 ++
t/unit-tests/clar/clar/fs.h | 522 +++++++++++++
t/unit-tests/clar/clar/print.h | 211 ++++++
t/unit-tests/clar/clar/sandbox.h | 153 ++++
t/unit-tests/clar/clar/summary.h | 143 ++++
t/unit-tests/clar/generate.py | 266 +++++++
t/unit-tests/clar/test/.gitignore | 4 +
t/unit-tests/clar/test/Makefile | 39 +
t/unit-tests/clar/test/clar_test.h | 16 +
t/unit-tests/clar/test/main.c | 40 +
t/unit-tests/clar/test/main.c.sample | 27 +
t/unit-tests/clar/test/resources/test/file | 1 +
t/unit-tests/clar/test/sample.c | 84 ++
20 files changed, 2943 insertions(+), 1 deletion(-)
create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
create mode 100644 t/unit-tests/clar/COPYING
create mode 100644 t/unit-tests/clar/README.md
create mode 100644 t/unit-tests/clar/clar.c
create mode 100644 t/unit-tests/clar/clar.h
create mode 100644 t/unit-tests/clar/clar/fixtures.h
create mode 100644 t/unit-tests/clar/clar/fs.h
create mode 100644 t/unit-tests/clar/clar/print.h
create mode 100644 t/unit-tests/clar/clar/sandbox.h
create mode 100644 t/unit-tests/clar/clar/summary.h
create mode 100755 t/unit-tests/clar/generate.py
create mode 100644 t/unit-tests/clar/test/.gitignore
create mode 100644 t/unit-tests/clar/test/Makefile
create mode 100644 t/unit-tests/clar/test/clar_test.h
create mode 100644 t/unit-tests/clar/test/main.c
create mode 100644 t/unit-tests/clar/test/main.c.sample
create mode 100644 t/unit-tests/clar/test/resources/test/file
create mode 100644 t/unit-tests/clar/test/sample.c
diff --git a/Documentation/technical/unit-tests.txt b/Documentation/technical/unit-tests.txt
index 206037ffb19..5a432b7b29c 100644
--- a/Documentation/technical/unit-tests.txt
+++ b/Documentation/technical/unit-tests.txt
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
:criterion: https://github.com/Snaipe/Criterion[Criterion]
:c-tap: https://github.com/rra/c-tap-harness/[C TAP]
:check: https://libcheck.github.io/check/[Check]
+:clar: https://github.com/clar-test/clar[Clar]
[format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
|=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
{criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
{c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
{check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
+{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
|=====
=== Additional framework candidates
diff --git a/Makefile b/Makefile
index a87e18b317d..28742a60964 100644
--- a/Makefile
+++ b/Makefile
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
+THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o
.PHONY: sparse
sparse: $(SP_OBJ)
-EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
+EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
ifndef OPENSSL_SHA1
EXCEPT_HDRS += sha1/openssl.h
endif
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml
new file mode 100644
index 00000000000..b1ac2de460a
--- /dev/null
+++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -0,0 +1,23 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ os: [ ubuntu-latest, macos-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Check out
+ uses: actions/checkout@v2
+ - name: Build
+ run: |
+ cd test
+ make
diff --git a/t/unit-tests/clar/COPYING b/t/unit-tests/clar/COPYING
new file mode 100644
index 00000000000..8983817f0c9
--- /dev/null
+++ b/t/unit-tests/clar/COPYING
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2011-2015 Vicent Marti
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/t/unit-tests/clar/README.md b/t/unit-tests/clar/README.md
new file mode 100644
index 00000000000..a8961c5f10f
--- /dev/null
+++ b/t/unit-tests/clar/README.md
@@ -0,0 +1,329 @@
+Come out and Clar
+=================
+
+In Catalan, "clar" means clear, easy to perceive. Using clar will make it
+easy to test and make clear the quality of your code.
+
+> _Historical note_
+>
+> Originally the clar project was named "clay" because the word "test" has its
+> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
+> meaning "piece of burned clay"?
+>
+> This is because historically, testing implied melting metal in a pot to
+> check its quality. Clay is what tests are made of.
+
+## Quick Usage Overview
+
+Clar is a minimal C unit testing framework. It's been written to replace the
+old framework in [libgit2][libgit2], but it's both very versatile and
+straightforward to use.
+
+Can you count to funk?
+
+- **Zero: Initialize test directory**
+
+ ~~~~ 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
+ ~~~~
+
+- **One: Write some tests**
+
+ File: tests/adding.c:
+
+ ~~~~ c
+ /* adding.c for the "Adding" suite */
+ #include "clar.h"
+
+ static int *answer;
+
+ void test_adding__initialize(void)
+ {
+ answer = malloc(sizeof(int));
+ cl_assert_(answer != NULL, "No memory left?");
+ *answer = 42;
+ }
+
+ void test_adding__cleanup(void)
+ {
+ free(answer);
+ }
+
+ void test_adding__make_sure_math_still_works(void)
+ {
+ cl_assert_(5 > 3, "Five should probably be greater than three");
+ cl_assert_(-5 < 2, "Negative numbers are small, I think");
+ cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
+ }
+ ~~~~~
+
+- **Two: Build the test executable**
+
+ ~~~~ sh
+ $ cd tests
+ $ $CLAR_PATH/generate.py .
+ Written `clar.suite` (1 suites)
+ $ gcc -I. clar.c main.c adding.c -o testit
+ ~~~~
+
+- **Funk: Funk it.**
+
+ ~~~~ sh
+ $ ./testit
+ ~~~~
+
+## The Clar Test Suite
+
+Writing a test suite is pretty straightforward. Each test suite is a `*.c`
+file with a descriptive name: this encourages modularity.
+
+Each test suite has optional initialize and cleanup methods. These methods
+will be called before and after running **each** test in the suite, even if
+such test fails. As a rule of thumb, if a test needs a different initializer
+or cleanup method than another test in the same module, that means it
+doesn't belong in that module. Keep that in mind when grouping tests
+together.
+
+The `initialize` and `cleanup` methods have the following syntax, with
+`suitename` being the current suite name, e.g. `adding` for the `adding.c`
+suite.
+
+~~~~ c
+void test_suitename__initialize(void)
+{
+ /* init */
+}
+
+void test_suitename__cleanup(void)
+{
+ /* cleanup */
+}
+~~~~
+
+These methods are encouraged to use static, global variables to store the state
+that will be used by all tests inside the suite.
+
+~~~~ c
+static git_repository *_repository;
+
+void test_status__initialize(void)
+{
+ create_tmp_repo(STATUS_REPO);
+ git_repository_open(_repository, STATUS_REPO);
+}
+
+void test_status__cleanup(void)
+{
+ git_repository_close(_repository);
+ git_path_rm(STATUS_REPO);
+}
+
+void test_status__simple_test(void)
+{
+ /* do something with _repository */
+}
+~~~~
+
+Writing the actual tests is just as straightforward. Tests have the
+`void test_suitename__test_name(void)` signature, and they should **not**
+be static. Clar will automatically detect and list them.
+
+Tests are run as they appear on their original suites: they have no return
+value. A test is considered "passed" if it doesn't raise any errors. Check
+the "Clar API" section to see the various helper functions to check and
+raise errors during test execution.
+
+__Caution:__ If you use assertions inside of `test_suitename__initialize`,
+make sure that you do not rely on `__initialize` being completely run
+inside your `test_suitename__cleanup` function. Otherwise you might
+encounter ressource cleanup twice.
+
+## How does Clar work?
+
+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`)
+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
+ executable
+5. run the executable to test!
+
+The Clar boilerplate gives you a set of useful test assertions and features
+(like accessing or making sandbox copies of fixture data). It consists of
+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
+`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.
+
+The Clar mixer (`generate.py`) recursively scans your test directory for
+any `.c` files, parses them, and writes the `clar.suite` file with all of
+the metadata about your tests. When you build, the `clar.suite` file is
+included into `clar.c`.
+
+The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
+
+Commandline usage of the mixer is as follows:
+
+ $ ./generate.py .
+
+Where `.` is the folder where all the test suites can be found. The mixer
+will automatically locate all the relevant source files and build the
+testing metadata. The metadata will be written to `clar.suite`, in the same
+folder as all the test suites. This file is included by `clar.c` and so
+must be accessible via `#include` when building the test executable.
+
+ $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
+
+**Note that the Clar mixer only needs to be ran when adding new tests to a
+suite, in order to regenerate the metadata**. As a result, the `clar.suite`
+file can be checked into version control if you wish to be able to build
+your test suite without having to re-run the mixer.
+
+This is handy when e.g. generating tests in a local computer, and then
+building and testing them on an embedded device or a platform where Python
+is not available.
+
+### Fixtures
+
+Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
+
+Once that's done, you can use the fixture API as defined below.
+
+## The Clar API
+
+Clar makes the following methods available from all functions in a test
+suite.
+
+- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
+ function call passes, in the POSIX sense (returns a value greater or equal
+ to 0).
+
+- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
+ function call fails, in the POSIX sense (returns a value less than 0).
+
+- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
+
+- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
+ given function call passes, in the POSIX sense (returns a value greater or
+ equal to 0). If the function call doesn't succeed, a test failure will be
+ logged but the test's execution will continue.
+
+- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
+ given function call fails, in the POSIX sense (returns a value less than
+ 0). If the function call doesn't fail, a test failure will be logged but
+ the test's execution will continue.
+
+- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
+ true, a test failure will be logged but the test's execution will continue.
+
+- `cl_fail(message)`: Fail the current test with the given message.
+
+- `cl_warning(message)`: Issue a warning. This warning will be
+ logged as a test failure but the test's execution will continue.
+
+- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
+ method for a single test. This method will be called with `opaque` as its
+ argument before the test returns (even if the test has failed).
+ If a global cleanup method is also available, the local cleanup will be
+ called first, and then the global.
+
+- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
+ The advantage of this over a simple `cl_assert` is that it will format
+ a much nicer error report if the values are not equal.
+
+- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
+ are equal. The expected value can also be NULL and this will correctly
+ test for that.
+
+- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
+ so that you can mutate the file directly.
+
+- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
+ sandbox.
+
+- `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.
+
+It's strongly encouraged to perform test assertions in auxiliary methods,
+instead of returning error values. This is considered good Clar style.
+
+Style Example:
+
+~~~~ c
+/*
+ * Bad style: auxiliary functions return an error code
+ */
+
+static int check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ if (aux == NULL)
+ return -1;
+
+ return strcmp(my_function(aux), str) == 0 ? 0 : -1;
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ cl_must_pass_(
+ check_string("foo"),
+ "String differs after processing"
+ );
+
+ cl_must_pass_(
+ check_string("bar"),
+ "String differs after processing"
+ );
+}
+~~~~
+
+~~~~ c
+/*
+ * Good style: auxiliary functions perform assertions
+ */
+
+static void check_string(const char *str)
+{
+ const char *aux = process_string(str);
+
+ cl_assert_(
+ aux != NULL,
+ "String processing failed"
+ );
+
+ cl_assert_(
+ strcmp(my_function(aux), str) == 0,
+ "String differs after processing"
+ );
+}
+
+void test_example__a_test_with_auxiliary_methods(void)
+{
+ check_string("foo");
+ 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.
+
+
+[libgit2]: https://github.com/libgit2/libgit2
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
new file mode 100644
index 00000000000..3fc2c768158
--- /dev/null
+++ b/t/unit-tests/clar/clar.c
@@ -0,0 +1,842 @@
+/*
+ * 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 <assert.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <time.h>
+
+/* required for sandboxing */
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <windows.h>
+# include <io.h>
+# include <shellapi.h>
+# include <direct.h>
+
+# define _MAIN_CC __cdecl
+
+# ifndef stat
+# define stat(path, st) _stat(path, st)
+# endif
+# ifndef mkdir
+# define mkdir(path, mode) _mkdir(path)
+# endif
+# ifndef chdir
+# define chdir(path) _chdir(path)
+# endif
+# ifndef access
+# define access(path, mode) _access(path, mode)
+# endif
+# ifndef strdup
+# define strdup(str) _strdup(str)
+# endif
+# ifndef strcasecmp
+# define strcasecmp(a,b) _stricmp(a,b)
+# endif
+
+# ifndef __MINGW32__
+# pragma comment(lib, "shell32")
+# ifndef strncpy
+# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
+# endif
+# ifndef W_OK
+# define W_OK 02
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
+# endif
+# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
+# else
+# define p_snprintf snprintf
+# endif
+
+# ifndef PRIuZ
+# define PRIuZ "Iu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "Ix"
+# endif
+
+# if defined(_MSC_VER) || defined(__MINGW32__)
+ typedef struct stat STAT_T;
+# else
+ typedef struct _stat STAT_T;
+# endif
+#else
+# include <sys/wait.h> /* waitpid(2) */
+# include <unistd.h>
+# define _MAIN_CC
+# define p_snprintf snprintf
+# ifndef PRIuZ
+# define PRIuZ "zu"
+# endif
+# ifndef PRIxZ
+# define PRIxZ "zx"
+# endif
+ typedef struct stat STAT_T;
+#endif
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+#include "clar.h"
+
+static void fs_rm(const char *_source);
+static void fs_copy(const char *_source, const char *dest);
+
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name);
+#endif
+
+struct clar_error {
+ const char *file;
+ const char *function;
+ size_t line_number;
+ const char *error_msg;
+ char *description;
+
+ struct clar_error *next;
+};
+
+struct clar_explicit {
+ size_t suite_idx;
+ const char *filter;
+
+ struct clar_explicit *next;
+};
+
+struct clar_report {
+ const char *test;
+ int test_number;
+ const char *suite;
+
+ enum cl_test_status status;
+ time_t start;
+ double elapsed;
+
+ struct clar_error *errors;
+ struct clar_error *last_error;
+
+ struct clar_report *next;
+};
+
+struct clar_summary {
+ const char *filename;
+ FILE *fp;
+};
+
+static struct {
+ enum cl_test_status test_status;
+
+ const char *active_test;
+ const char *active_suite;
+
+ int total_skipped;
+ int total_errors;
+
+ int tests_ran;
+ int suites_ran;
+
+ enum cl_output_format output_format;
+
+ int report_errors_only;
+ int exit_on_error;
+ int verbosity;
+
+ int write_summary;
+ char *summary_filename;
+ struct clar_summary *summary;
+
+ struct clar_explicit *explicit;
+ struct clar_explicit *last_explicit;
+
+ struct clar_report *reports;
+ struct clar_report *last_report;
+
+ void (*local_cleanup)(void *);
+ void *local_cleanup_payload;
+
+ jmp_buf trampoline;
+ int trampoline_enabled;
+
+ cl_trace_cb *pfn_trace_cb;
+ void *trace_payload;
+
+} _clar;
+
+struct clar_func {
+ const char *name;
+ void (*ptr)(void);
+};
+
+struct clar_suite {
+ const char *name;
+ struct clar_func initialize;
+ struct clar_func cleanup;
+ const struct clar_func *tests;
+ size_t test_count;
+ int enabled;
+};
+
+/* From clar_print_*.c */
+static void clar_print_init(int test_count, int suite_count, const char *suite_names);
+static void clar_print_shutdown(int test_count, int suite_count, int error_count);
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
+static void clar_print_onsuite(const char *suite_name, int suite_index);
+static void clar_print_onabort(const char *msg, ...);
+
+/* From clar_sandbox.c */
+static void clar_unsandbox(void);
+static int clar_sandbox(void);
+
+/* From summary.h */
+static struct clar_summary *clar_summary_init(const char *filename);
+static int clar_summary_shutdown(struct clar_summary *fp);
+
+/* Load the declarations for the test suite */
+#include "clar.suite"
+
+
+#define CL_TRACE(ev) \
+ do { \
+ if (_clar.pfn_trace_cb) \
+ _clar.pfn_trace_cb(ev, \
+ _clar.active_suite, \
+ _clar.active_test, \
+ _clar.trace_payload); \
+ } while (0)
+
+void cl_trace_register(cl_trace_cb *cb, void *payload)
+{
+ _clar.pfn_trace_cb = cb;
+ _clar.trace_payload = payload;
+}
+
+
+/* Core test functions */
+static void
+clar_report_errors(struct clar_report *report)
+{
+ struct clar_error *error;
+ int i = 1;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, _clar.last_report, error);
+}
+
+static void
+clar_report_all(void)
+{
+ struct clar_report *report;
+ struct clar_error *error;
+ int i = 1;
+
+ for (report = _clar.reports; report; report = report->next) {
+ if (report->status != CL_TEST_FAILURE)
+ continue;
+
+ for (error = report->errors; error; error = error->next)
+ clar_print_error(i++, report, error);
+ }
+}
+
+#ifdef WIN32
+# define clar_time DWORD
+
+static void clar_time_now(clar_time *out)
+{
+ *out = GetTickCount();
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)*end - (double)*start) / 1000;
+}
+#else
+# include <sys/time.h>
+
+# define clar_time struct timeval
+
+static void clar_time_now(clar_time *out)
+{
+ struct timezone tz;
+
+ gettimeofday(out, &tz);
+}
+
+static double clar_time_diff(clar_time *start, clar_time *end)
+{
+ return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
+ ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
+}
+#endif
+
+static void
+clar_run_test(
+ const struct clar_suite *suite,
+ const struct clar_func *test,
+ const struct clar_func *initialize,
+ const struct clar_func *cleanup)
+{
+ clar_time start, end;
+
+ _clar.trampoline_enabled = 1;
+
+ CL_TRACE(CL_TRACE__TEST__BEGIN);
+
+ _clar.last_report->start = time(NULL);
+ clar_time_now(&start);
+
+ if (setjmp(_clar.trampoline) == 0) {
+ if (initialize->ptr != NULL)
+ initialize->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
+ test->ptr();
+ CL_TRACE(CL_TRACE__TEST__RUN_END);
+ }
+
+ clar_time_now(&end);
+
+ _clar.trampoline_enabled = 0;
+
+ if (_clar.last_report->status == CL_TEST_NOTRUN)
+ _clar.last_report->status = CL_TEST_OK;
+
+ _clar.last_report->elapsed = clar_time_diff(&start, &end);
+
+ if (_clar.local_cleanup != NULL)
+ _clar.local_cleanup(_clar.local_cleanup_payload);
+
+ if (cleanup->ptr != NULL)
+ cleanup->ptr();
+
+ CL_TRACE(CL_TRACE__TEST__END);
+
+ _clar.tests_ran++;
+
+ /* remove any local-set cleanup methods */
+ _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);
+ }
+}
+
+static void
+clar_run_suite(const struct clar_suite *suite, const char *filter)
+{
+ const struct clar_func *test = suite->tests;
+ size_t i, matchlen;
+ struct clar_report *report;
+ int exact = 0;
+
+ if (!suite->enabled)
+ return;
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+
+ if (!_clar.report_errors_only)
+ clar_print_onsuite(suite->name, ++_clar.suites_ran);
+
+ _clar.active_suite = suite->name;
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_BEGIN);
+
+ if (filter) {
+ size_t suitelen = strlen(suite->name);
+ matchlen = strlen(filter);
+ if (matchlen <= suitelen) {
+ filter = NULL;
+ } else {
+ filter += suitelen;
+ while (*filter == ':')
+ ++filter;
+ matchlen = strlen(filter);
+
+ if (matchlen && filter[matchlen - 1] == '$') {
+ exact = 1;
+ matchlen--;
+ }
+ }
+ }
+
+ for (i = 0; i < suite->test_count; ++i) {
+ if (filter && strncmp(test[i].name, filter, matchlen))
+ continue;
+
+ if (exact && strlen(test[i].name) != matchlen)
+ continue;
+
+ _clar.active_test = test[i].name;
+
+ report = calloc(1, sizeof(struct clar_report));
+ report->suite = _clar.active_suite;
+ report->test = _clar.active_test;
+ report->test_number = _clar.tests_ran;
+ report->status = CL_TEST_NOTRUN;
+
+ if (_clar.reports == NULL)
+ _clar.reports = report;
+
+ if (_clar.last_report != NULL)
+ _clar.last_report->next = report;
+
+ _clar.last_report = report;
+
+ clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
+
+ if (_clar.exit_on_error && _clar.total_errors)
+ return;
+ }
+
+ _clar.active_test = NULL;
+ CL_TRACE(CL_TRACE__SUITE_END);
+}
+
+static void
+clar_usage(const char *arg)
+{
+ printf("Usage: %s [options]\n\n", arg);
+ printf("Options:\n");
+ printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
+ 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 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);
+}
+
+static void
+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) {
+ clar_usage(argv[0]);
+ }
+ }
+
+ for (i = 1; i < argc; ++i) {
+ char *argument = argv[i];
+
+ switch (argument[1]) {
+ case 's':
+ case 'i':
+ case 'x': { /* given suite name */
+ int offset = (argument[2] == '=') ? 3 : 2, found = 0;
+ char action = argument[1];
+ size_t j, arglen, suitelen, cmplen;
+
+ argument += offset;
+ arglen = strlen(argument);
+
+ if (arglen == 0)
+ clar_usage(argv[0]);
+
+ for (j = 0; j < _clar_suite_count; ++j) {
+ suitelen = strlen(_clar_suites[j].name);
+ cmplen = (arglen < suitelen) ? arglen : suitelen;
+
+ if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
+ int exact = (arglen >= suitelen);
+
+ /* Do we have a real suite prefix separated by a
+ * trailing '::' or just a matching substring? */
+ if (arglen > suitelen && (argument[suitelen] != ':'
+ || argument[suitelen + 1] != ':'))
+ continue;
+
+ ++found;
+
+ if (!exact)
+ _clar.verbosity = MAX(_clar.verbosity, 1);
+
+ switch (action) {
+ case 's': {
+ struct clar_explicit *explicit =
+ calloc(1, sizeof(struct clar_explicit));
+ assert(explicit);
+
+ explicit->suite_idx = j;
+ explicit->filter = argument;
+
+ if (_clar.explicit == NULL)
+ _clar.explicit = explicit;
+
+ if (_clar.last_explicit != NULL)
+ _clar.last_explicit->next = explicit;
+
+ _clar_suites[j].enabled = 1;
+ _clar.last_explicit = explicit;
+ break;
+ }
+ case 'i': _clar_suites[j].enabled = 1; break;
+ case 'x': _clar_suites[j].enabled = 0; break;
+ }
+
+ if (exact)
+ break;
+ }
+ }
+
+ if (!found) {
+ clar_print_onabort("No suite matching '%s' found.\n", argument);
+ exit(-1);
+ }
+ break;
+ }
+
+ case 'q':
+ _clar.report_errors_only = 1;
+ break;
+
+ case 'Q':
+ _clar.exit_on_error = 1;
+ break;
+
+ case 't':
+ _clar.output_format = CL_OUTPUT_TAP;
+ break;
+
+ case 'l': {
+ size_t j;
+ 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);
+
+ exit(0);
+ }
+
+ case 'v':
+ _clar.verbosity++;
+ break;
+
+ case 'r':
+ _clar.write_summary = 1;
+ free(_clar.summary_filename);
+ _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
+ break;
+
+ default:
+ assert(!"Unexpected commandline argument!");
+ }
+ }
+}
+
+void
+clar_test_init(int argc, char **argv)
+{
+ const char *summary_env;
+
+ if (argc > 1)
+ clar_parse_args(argc, argv);
+
+ clar_print_init(
+ (int)_clar_callback_count,
+ (int)_clar_suite_count,
+ ""
+ );
+
+ if (!_clar.summary_filename &&
+ (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
+ _clar.write_summary = 1;
+ _clar.summary_filename = strdup(summary_env);
+ }
+
+ if (_clar.write_summary && !_clar.summary_filename)
+ _clar.summary_filename = strdup("summary.xml");
+
+ if (_clar.write_summary &&
+ !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
+ clar_print_onabort("Failed to open the summary file\n");
+ exit(-1);
+ }
+
+ if (clar_sandbox() < 0) {
+ clar_print_onabort("Failed to sandbox the test runner.\n");
+ exit(-1);
+ }
+}
+
+int
+clar_test_run(void)
+{
+ size_t i;
+ struct clar_explicit *explicit;
+
+ if (_clar.explicit) {
+ for (explicit = _clar.explicit; explicit; explicit = explicit->next)
+ clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
+ } else {
+ for (i = 0; i < _clar_suite_count; ++i)
+ clar_run_suite(&_clar_suites[i], NULL);
+ }
+
+ return _clar.total_errors;
+}
+
+void
+clar_test_shutdown(void)
+{
+ struct clar_explicit *explicit, *explicit_next;
+ struct clar_report *report, *report_next;
+
+ clar_print_shutdown(
+ _clar.tests_ran,
+ (int)_clar_suite_count,
+ _clar.total_errors
+ );
+
+ clar_unsandbox();
+
+ if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
+ clar_print_onabort("Failed to write the summary file\n");
+ exit(-1);
+ }
+
+ for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
+ explicit_next = explicit->next;
+ free(explicit);
+ }
+
+ for (report = _clar.reports; report; report = report_next) {
+ report_next = report->next;
+ free(report);
+ }
+
+ free(_clar.summary_filename);
+}
+
+int
+clar_test(int argc, char **argv)
+{
+ int errors;
+
+ clar_test_init(argc, argv);
+ errors = clar_test_run();
+ clar_test_shutdown();
+
+ return errors;
+}
+
+static void abort_test(void)
+{
+ if (!_clar.trampoline_enabled) {
+ clar_print_onabort(
+ "Fatal error: a cleanup method raised an exception.");
+ clar_report_errors(_clar.last_report);
+ exit(-1);
+ }
+
+ CL_TRACE(CL_TRACE__TEST__LONGJMP);
+ longjmp(_clar.trampoline, -1);
+}
+
+void clar__skip(void)
+{
+ _clar.last_report->status = CL_TEST_SKIP;
+ _clar.total_skipped++;
+ abort_test();
+}
+
+void clar__fail(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ struct clar_error *error = calloc(1, sizeof(struct clar_error));
+
+ if (_clar.last_report->errors == NULL)
+ _clar.last_report->errors = error;
+
+ if (_clar.last_report->last_error != NULL)
+ _clar.last_report->last_error->next = error;
+
+ _clar.last_report->last_error = error;
+
+ error->file = file;
+ error->function = function;
+ error->line_number = line;
+ error->error_msg = error_msg;
+
+ if (description != NULL)
+ error->description = strdup(description);
+
+ _clar.total_errors++;
+ _clar.last_report->status = CL_TEST_FAILURE;
+
+ if (should_abort)
+ abort_test();
+}
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *error_msg,
+ const char *description,
+ int should_abort)
+{
+ if (condition)
+ return;
+
+ clar__fail(file, function, line, error_msg, description, should_abort);
+}
+
+void clar__assert_equal(
+ const char *file,
+ const char *function,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...)
+{
+ va_list args;
+ char buf[4096];
+ int is_equal = 1;
+
+ va_start(args, fmt);
+
+ if (!strcmp("%s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
+ s1, s2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
+ }
+ }
+ }
+ else if(!strcmp("%.*s", fmt)) {
+ const char *s1 = va_arg(args, const char *);
+ const char *s2 = va_arg(args, const char *);
+ int len = va_arg(args, int);
+ is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
+
+ if (!is_equal) {
+ if (s1 && s2) {
+ int pos;
+ for (pos = 0; s1[pos] == s2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
+ /* find differing byte offset */;
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
+ wcs1, wcs2, pos);
+ } else {
+ p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
+ }
+ }
+ }
+ else if(!strcmp("%.*ls", fmt)) {
+ const wchar_t *wcs1 = va_arg(args, const wchar_t *);
+ const wchar_t *wcs2 = va_arg(args, const wchar_t *);
+ int len = va_arg(args, int);
+ is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
+
+ if (!is_equal) {
+ if (wcs1 && wcs2) {
+ int pos;
+ for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++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);
+ }
+ }
+ }
+ else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
+ size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
+ is_equal = (sz1 == sz2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
+ }
+ }
+ else if (!strcmp("%p", fmt)) {
+ 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);
+ }
+ else {
+ int i1 = va_arg(args, int), i2 = va_arg(args, int);
+ is_equal = (i1 == i2);
+ if (!is_equal) {
+ int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
+ strncat(buf, " != ", sizeof(buf) - offset);
+ p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
+ }
+ }
+
+ va_end(args);
+
+ if (!is_equal)
+ clar__fail(file, function, line, err, buf, should_abort);
+}
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
+{
+ _clar.local_cleanup = cleanup;
+ _clar.local_cleanup_payload = opaque;
+}
+
+#include "clar/sandbox.h"
+#include "clar/fixtures.h"
+#include "clar/fs.h"
+#include "clar/print.h"
+#include "clar/summary.h"
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h
new file mode 100644
index 00000000000..8c22382bd56
--- /dev/null
+++ b/t/unit-tests/clar/clar.h
@@ -0,0 +1,173 @@
+/*
+ * 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_H__
+#define __CLAR_TEST_H__
+
+#include <stdlib.h>
+
+enum cl_test_status {
+ CL_TEST_OK,
+ CL_TEST_FAILURE,
+ CL_TEST_SKIP,
+ CL_TEST_NOTRUN,
+};
+
+enum cl_output_format {
+ CL_OUTPUT_CLAP,
+ CL_OUTPUT_TAP,
+};
+
+/** Setup clar environment */
+void clar_test_init(int argc, char *argv[]);
+int clar_test_run(void);
+void clar_test_shutdown(void);
+
+/** One shot setup & run */
+int clar_test(int argc, char *argv[]);
+
+const char *clar_sandbox_path(void);
+
+void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
+void cl_fs_cleanup(void);
+
+/**
+ * cl_trace_* is a hook to provide a simple global tracing
+ * mechanism.
+ *
+ * The goal here is to let main() provide clar-proper
+ * with a callback to optionally write log info for
+ * test operations into the same stream used by their
+ * actual tests. This would let them print test names
+ * and maybe performance data as they choose.
+ *
+ * The goal is NOT to alter the flow of control or to
+ * override test selection/skipping. (So the callback
+ * does not return a value.)
+ *
+ * The goal is NOT to duplicate the existing
+ * pass/fail/skip reporting. (So the callback
+ * does not accept a status/errorcode argument.)
+ *
+ */
+typedef enum cl_trace_event {
+ CL_TRACE__SUITE_BEGIN,
+ CL_TRACE__SUITE_END,
+ CL_TRACE__TEST__BEGIN,
+ CL_TRACE__TEST__END,
+ CL_TRACE__TEST__RUN_BEGIN,
+ CL_TRACE__TEST__RUN_END,
+ CL_TRACE__TEST__LONGJMP,
+} cl_trace_event;
+
+typedef void (cl_trace_cb)(
+ cl_trace_event ev,
+ const char *suite_name,
+ const char *test_name,
+ void *payload);
+
+/**
+ * Register a callback into CLAR to send global trace events.
+ * Pass NULL to disable.
+ */
+void cl_trace_register(cl_trace_cb *cb, void *payload);
+
+
+#ifdef CLAR_FIXTURE_PATH
+const char *cl_fixture(const char *fixture_name);
+void cl_fixture_sandbox(const char *fixture_name);
+void cl_fixture_cleanup(const char *fixture_name);
+const char *cl_fixture_basename(const char *fixture_name);
+#endif
+
+/**
+ * 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)
+
+/**
+ * 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)
+
+/**
+ * Assertion macros with no error message
+ */
+#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
+#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
+#define cl_assert(expr) cl_assert_(expr, NULL)
+
+/**
+ * Check macros with no error message
+ */
+#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
+#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
+#define cl_check(expr) cl_check_(expr, NULL)
+
+/**
+ * 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_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_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_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_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_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_b(b1,b2) clar__assert_equal(__FILE__,__func__,__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))
+
+void clar__skip(void);
+
+void clar__fail(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert(
+ int condition,
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *error,
+ const char *description,
+ int should_abort);
+
+void clar__assert_equal(
+ const char *file,
+ const char *func,
+ size_t line,
+ const char *err,
+ int should_abort,
+ const char *fmt,
+ ...);
+
+#endif
diff --git a/t/unit-tests/clar/clar/fixtures.h b/t/unit-tests/clar/clar/fixtures.h
new file mode 100644
index 00000000000..6ec6423484d
--- /dev/null
+++ b/t/unit-tests/clar/clar/fixtures.h
@@ -0,0 +1,50 @@
+#ifdef CLAR_FIXTURE_PATH
+static const char *
+fixture_path(const char *base, const char *fixture_name)
+{
+ static char _path[4096];
+ size_t root_len;
+
+ root_len = strlen(base);
+ strncpy(_path, base, sizeof(_path));
+
+ if (_path[root_len - 1] != '/')
+ _path[root_len++] = '/';
+
+ if (fixture_name[0] == '/')
+ fixture_name++;
+
+ strncpy(_path + root_len,
+ fixture_name,
+ sizeof(_path) - root_len);
+
+ return _path;
+}
+
+const char *cl_fixture(const char *fixture_name)
+{
+ return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
+}
+
+void cl_fixture_sandbox(const char *fixture_name)
+{
+ fs_copy(cl_fixture(fixture_name), _clar_path);
+}
+
+const char *cl_fixture_basename(const char *fixture_name)
+{
+ const char *p;
+
+ for (p = fixture_name; *p; p++) {
+ if (p[0] == '/' && p[1] && p[1] != '/')
+ fixture_name = p+1;
+ }
+
+ return fixture_name;
+}
+
+void cl_fixture_cleanup(const char *fixture_name)
+{
+ fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
+}
+#endif
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
new file mode 100644
index 00000000000..3e39890bd3e
--- /dev/null
+++ b/t/unit-tests/clar/clar/fs.h
@@ -0,0 +1,522 @@
+/*
+ * By default, use a read/write loop to copy files on POSIX systems.
+ * On Linux, use sendfile by default as it's slightly faster. On
+ * macOS, we avoid fcopyfile by default because it's slightly slower.
+ */
+#undef USE_FCOPYFILE
+#define USE_SENDFILE 1
+
+#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
+
+#ifdef __MINGW32__
+
+/* These security-enhanced functions are not available
+ * in MinGW, so just use the vanilla ones */
+#define wcscpy_s(a, b, c) wcscpy((a), (c))
+#define wcscat_s(a, b, c) wcscat((a), (c))
+
+#endif /* __MINGW32__ */
+
+static int
+fs__dotordotdot(WCHAR *_tocheck)
+{
+ return _tocheck[0] == '.' &&
+ (_tocheck[1] == '\0' ||
+ (_tocheck[1] == '.' && _tocheck[2] == '\0'));
+}
+
+static int
+fs_rmdir_rmdir(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+
+ while (!RemoveDirectoryW(_wpath)) {
+ /* Only retry when we have retries remaining, and the
+ * error was ERROR_DIR_NOT_EMPTY. */
+ if (retries++ > RM_RETRY_COUNT ||
+ ERROR_DIR_NOT_EMPTY != GetLastError())
+ return -1;
+
+ /* Give whatever has a handle to a child item some time
+ * to release it before trying again */
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+
+ return 0;
+}
+
+static void translate_path(WCHAR *path, size_t path_size)
+{
+ size_t path_len, i;
+
+ if (wcsncmp(path, L"\\\\?\\", 4) == 0)
+ return;
+
+ path_len = wcslen(path);
+ cl_assert(path_size > path_len + 4);
+
+ for (i = path_len; i > 0; i--) {
+ WCHAR c = path[i - 1];
+
+ if (c == L'/')
+ path[i + 3] = L'\\';
+ else
+ path[i + 3] = path[i - 1];
+ }
+
+ path[0] = L'\\';
+ path[1] = L'\\';
+ path[2] = L'?';
+ path[3] = L'\\';
+ path[path_len + 4] = L'\0';
+}
+
+static void
+fs_rmdir_helper(WCHAR *_wsource)
+{
+ WCHAR buffer[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buffer_prefix_len;
+
+ /* Set up the buffer and capture the length */
+ wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
+ translate_path(buffer, CLAR_MAX_PATH);
+ wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
+ buffer_prefix_len = wcslen(buffer);
+
+ /* FindFirstFile needs a wildcard to match multiple items */
+ wcscat_s(buffer, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buffer, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_rmdir_helper(buffer);
+ else {
+ /* If set, the +R bit must be cleared before deleting */
+ if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
+ cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(buffer));
+ }
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+
+ /* Now that the directory is empty, remove it */
+ cl_assert(0 == fs_rmdir_rmdir(_wsource));
+}
+
+static int
+fs_rm_wait(WCHAR *_wpath)
+{
+ unsigned retries = 1;
+ DWORD last_error;
+
+ do {
+ if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
+ last_error = GetLastError();
+ else
+ last_error = ERROR_SUCCESS;
+
+ /* Is the item gone? */
+ if (ERROR_FILE_NOT_FOUND == last_error ||
+ ERROR_PATH_NOT_FOUND == last_error)
+ return 0;
+
+ Sleep(RM_RETRY_DELAY * retries * retries);
+ }
+ while (retries++ <= RM_RETRY_COUNT);
+
+ return -1;
+}
+
+static void
+fs_rm(const char *_source)
+{
+ WCHAR wsource[CLAR_MAX_PATH];
+ DWORD attrs;
+
+ /* The input path is UTF-8. Convert it to wide characters
+ * for use with the Windows API */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1, /* Indicates NULL termination */
+ wsource,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+
+ /* Does the item exist? If not, we have no work to do */
+ attrs = GetFileAttributesW(wsource);
+
+ if (INVALID_FILE_ATTRIBUTES == attrs)
+ return;
+
+ if (FILE_ATTRIBUTE_DIRECTORY & attrs)
+ fs_rmdir_helper(wsource);
+ else {
+ /* The item is a file. Strip the +R bit */
+ if (FILE_ATTRIBUTE_READONLY & attrs)
+ cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
+
+ cl_assert(DeleteFileW(wsource));
+ }
+
+ /* Wait for the DeleteFile or RemoveDirectory call to complete */
+ cl_assert(0 == fs_rm_wait(wsource));
+}
+
+static void
+fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
+{
+ WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+ size_t buf_source_prefix_len, buf_dest_prefix_len;
+
+ wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_source, CLAR_MAX_PATH);
+ buf_source_prefix_len = wcslen(buf_source);
+
+ wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
+ wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
+ translate_path(buf_dest, CLAR_MAX_PATH);
+ buf_dest_prefix_len = wcslen(buf_dest);
+
+ /* Get an enumerator for the items in the source. */
+ wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
+ find_handle = FindFirstFileW(buf_source, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+
+ /* Create the target directory. */
+ cl_assert(CreateDirectoryW(_wdest, NULL));
+
+ do {
+ /* FindFirstFile/FindNextFile gives back . and ..
+ * entries at the beginning */
+ if (fs__dotordotdot(find_data.cFileName))
+ continue;
+
+ wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
+ wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
+
+ if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
+ fs_copydir_helper(buf_source, buf_dest);
+ else
+ cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
+ }
+ while (FindNextFileW(find_handle, &find_data));
+
+ /* Ensure that we successfully completed the enumeration */
+ cl_assert(ERROR_NO_MORE_FILES == GetLastError());
+
+ /* Close the find handle */
+ FindClose(find_handle);
+}
+
+static void
+fs_copy(const char *_source, const char *_dest)
+{
+ WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
+ DWORD source_attrs, dest_attrs;
+ HANDLE find_handle;
+ WIN32_FIND_DATAW find_data;
+
+ /* The input paths are UTF-8. Convert them to wide characters
+ * for use with the Windows API. */
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _source,
+ -1,
+ wsource,
+ CLAR_MAX_PATH));
+
+ cl_assert(MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ _dest,
+ -1,
+ wdest,
+ CLAR_MAX_PATH));
+
+ translate_path(wsource, CLAR_MAX_PATH);
+ translate_path(wdest, CLAR_MAX_PATH);
+
+ /* Check the source for existence */
+ source_attrs = GetFileAttributesW(wsource);
+ cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
+
+ /* Check the target for existence */
+ dest_attrs = GetFileAttributesW(wdest);
+
+ if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
+ /* Target exists; append last path part of source to target.
+ * Use FindFirstFile to parse the path */
+ find_handle = FindFirstFileW(wsource, &find_data);
+ cl_assert(INVALID_HANDLE_VALUE != find_handle);
+ wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
+ wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
+ FindClose(find_handle);
+
+ /* Check the new target for existence */
+ cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
+ }
+
+ if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
+ fs_copydir_helper(wsource, wdest);
+ else
+ cl_assert(CopyFileW(wsource, wdest, TRUE));
+}
+
+void
+cl_fs_cleanup(void)
+{
+#ifdef CLAR_FIXTURE_PATH
+ fs_rm(fixture_path(_clar_path, "*"));
+#endif
+}
+
+#else
+
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__linux__)
+# include <sys/sendfile.h>
+#endif
+
+#if defined(__APPLE__)
+# include <copyfile.h>
+#endif
+
+static void basename_r(const char **out, int *out_len, const char *in)
+{
+ size_t in_len = strlen(in), start_pos;
+
+ for (in_len = strlen(in); in_len; in_len--) {
+ if (in[in_len - 1] != '/')
+ break;
+ }
+
+ for (start_pos = in_len; start_pos; start_pos--) {
+ if (in[start_pos - 1] == '/')
+ break;
+ }
+
+ cl_assert(in_len - start_pos < INT_MAX);
+
+ if (in_len - start_pos > 0) {
+ *out = &in[start_pos];
+ *out_len = (in_len - start_pos);
+ } else {
+ *out = "/";
+ *out_len = 1;
+ }
+}
+
+static char *joinpath(const char *dir, const char *base, int base_len)
+{
+ char *out;
+ int len;
+
+ if (base_len == -1) {
+ size_t bl = strlen(base);
+
+ cl_assert(bl < INT_MAX);
+ base_len = (int)bl;
+ }
+
+ len = strlen(dir) + base_len + 2;
+ cl_assert(len > 0);
+
+ cl_assert(out = malloc(len));
+ cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
+
+ return out;
+}
+
+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");
+ while ((d = (errno = 0, readdir(source_dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(source, d->d_name, -1);
+ fs_copy(child, dest);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+
+ closedir(source_dir);
+}
+
+static void
+fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
+{
+ int in, out;
+
+ cl_must_pass((in = open(source, O_RDONLY)));
+ cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
+
+#if USE_FCOPYFILE && defined(__APPLE__)
+ ((void)(source_len)); /* unused */
+ cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
+#elif USE_SENDFILE && defined(__linux__)
+ {
+ ssize_t ret = 0;
+
+ while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
+ source_len -= (size_t)ret;
+ }
+ cl_assert(ret >= 0);
+ }
+#else
+ {
+ char buf[131072];
+ ssize_t ret;
+
+ ((void)(source_len)); /* unused */
+
+ while ((ret = read(in, buf, sizeof(buf))) > 0) {
+ size_t len = (size_t)ret;
+
+ while (len && (ret = write(out, buf, len)) > 0) {
+ cl_assert(ret <= (ssize_t)len);
+ len -= ret;
+ }
+ cl_assert(ret >= 0);
+ }
+ cl_assert(ret == 0);
+ }
+#endif
+
+ close(in);
+ close(out);
+}
+
+static void
+fs_copy(const char *source, const char *_dest)
+{
+ char *dbuf = NULL;
+ const char *dest = NULL;
+ struct stat source_st, dest_st;
+
+ cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
+
+ if (lstat(_dest, &dest_st) == 0) {
+ const char *base;
+ int base_len;
+
+ /* Target exists and is directory; append basename */
+ cl_assert(S_ISDIR(dest_st.st_mode));
+
+ basename_r(&base, &base_len, source);
+ cl_assert(base_len < INT_MAX);
+
+ dbuf = joinpath(_dest, base, base_len);
+ dest = dbuf;
+ } else if (errno != ENOENT) {
+ cl_fail("Cannot copy; cannot stat destination");
+ } else {
+ dest = _dest;
+ }
+
+ if (S_ISDIR(source_st.st_mode)) {
+ fs_copydir_helper(source, dest, source_st.st_mode);
+ } else {
+ fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
+ }
+
+ free(dbuf);
+}
+
+static void
+fs_rmdir_helper(const char *path)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ cl_assert_(dir = opendir(path), "Could not open dir");
+ while ((d = (errno = 0, readdir(dir))) != NULL) {
+ char *child;
+
+ if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+ continue;
+
+ child = joinpath(path, d->d_name, -1);
+ fs_rm(child);
+ free(child);
+ }
+
+ cl_assert_(errno == 0, "Failed to iterate source dir");
+ closedir(dir);
+
+ cl_must_pass_(rmdir(path), "Could not remove directory");
+}
+
+static void
+fs_rm(const char *path)
+{
+ struct stat st;
+
+ if (lstat(path, &st)) {
+ if (errno == ENOENT)
+ return;
+
+ cl_fail("Cannot copy; cannot stat destination");
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ fs_rmdir_helper(path);
+ } else {
+ cl_must_pass(unlink(path));
+ }
+}
+
+void
+cl_fs_cleanup(void)
+{
+ clar_unsandbox();
+ clar_sandbox();
+}
+#endif
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h
new file mode 100644
index 00000000000..c17e2f693bd
--- /dev/null
+++ b/t/unit-tests/clar/clar/print.h
@@ -0,0 +1,211 @@
+/* clap: clar protocol, the traditional clar output format */
+
+static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
+ printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
+}
+
+static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)error_count;
+
+ printf("\n\n");
+ clar_report_all();
+}
+
+static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ printf(" %d) Failure:\n", num);
+
+ printf("%s::%s [%s:%"PRIuZ"]\n",
+ report->suite,
+ report->test,
+ error->file,
+ error->line_number);
+
+ printf(" %s\n", error->error_msg);
+
+ if (error->description != NULL)
+ printf(" %s\n", error->description);
+
+ printf("\n");
+ fflush(stdout);
+}
+
+static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ (void)test_name;
+ (void)test_number;
+
+ 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;
+ }
+ } else {
+ switch (status) {
+ case CL_TEST_OK: printf("."); break;
+ case CL_TEST_FAILURE: printf("F"); break;
+ case CL_TEST_SKIP: printf("S"); break;
+ case CL_TEST_NOTRUN: printf("N"); break;
+ }
+
+ fflush(stdout);
+ }
+}
+
+static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
+{
+ if (_clar.verbosity == 1)
+ printf("\n%s", suite_name);
+
+ (void)suite_index;
+}
+
+static void clar_print_clap_onabort(const char *fmt, va_list arg)
+{
+ vfprintf(stderr, fmt, arg);
+}
+
+/* tap: test anywhere protocol format */
+
+static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
+{
+ (void)test_count;
+ (void)suite_count;
+ (void)suite_names;
+ printf("TAP version 13\n");
+}
+
+static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
+{
+ (void)suite_count;
+ (void)error_count;
+
+ printf("1..%d\n", test_count);
+}
+
+static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ (void)num;
+ (void)report;
+ (void)error;
+}
+
+static void print_escaped(const char *str)
+{
+ char *c;
+
+ while ((c = strchr(str, '\'')) != NULL) {
+ printf("%.*s", (int)(c - str), str);
+ printf("''");
+ str = c + 1;
+ }
+
+ printf("%s", str);
+}
+
+static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ const struct clar_error *error = _clar.last_report->errors;
+
+ (void)test_name;
+ (void)test_number;
+
+ switch(status) {
+ case CL_TEST_OK:
+ printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
+ break;
+ 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 (error->description)
+ printf(" %s\n", error->description);
+
+ printf(" at:\n");
+ printf(" file: '"); print_escaped(error->file); printf("'\n");
+ printf(" line: %" PRIuZ "\n", error->line_number);
+ printf(" function: '%s'\n", error->function);
+ printf(" ---\n");
+
+ break;
+ case CL_TEST_SKIP:
+ case CL_TEST_NOTRUN:
+ printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
+ break;
+ }
+
+ fflush(stdout);
+}
+
+static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
+{
+ printf("# start of suite %d: %s\n", suite_index, suite_name);
+}
+
+static void clar_print_tap_onabort(const char *fmt, va_list arg)
+{
+ printf("Bail out! ");
+ vprintf(fmt, arg);
+ fflush(stdout);
+}
+
+/* indirection between protocol output selection */
+
+#define PRINT(FN, ...) do { \
+ switch (_clar.output_format) { \
+ case CL_OUTPUT_CLAP: \
+ clar_print_clap_##FN (__VA_ARGS__); \
+ break; \
+ case CL_OUTPUT_TAP: \
+ clar_print_tap_##FN (__VA_ARGS__); \
+ break; \
+ default: \
+ abort(); \
+ } \
+ } while (0)
+
+static void clar_print_init(int test_count, int suite_count, const char *suite_names)
+{
+ PRINT(init, test_count, suite_count, suite_names);
+}
+
+static void clar_print_shutdown(int test_count, int suite_count, int error_count)
+{
+ PRINT(shutdown, test_count, suite_count, error_count);
+}
+
+static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
+{
+ PRINT(error, num, report, error);
+}
+
+static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
+{
+ PRINT(ontest, suite_name, test_name, test_number, status);
+}
+
+static void clar_print_onsuite(const char *suite_name, int suite_index)
+{
+ PRINT(onsuite, suite_name, suite_index);
+}
+
+static void clar_print_onabort(const char *msg, ...)
+{
+ va_list argp;
+ va_start(argp, msg);
+ PRINT(onabort, msg, argp);
+ va_end(argp);
+}
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
new file mode 100644
index 00000000000..7c177f35258
--- /dev/null
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -0,0 +1,153 @@
+#ifdef __APPLE__
+#include <sys/syslimits.h>
+#endif
+
+static char _clar_path[4096 + 1];
+
+static int
+is_valid_tmp_path(const char *path)
+{
+ STAT_T st;
+
+ if (stat(path, &st) != 0)
+ return 0;
+
+ if (!S_ISDIR(st.st_mode))
+ return 0;
+
+ return (access(path, W_OK) == 0);
+}
+
+static int
+find_tmp_path(char *buffer, size_t length)
+{
+#ifndef _WIN32
+ static const size_t var_count = 5;
+ static const char *env_vars[] = {
+ "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
+ };
+
+ size_t i;
+
+ 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;
+ }
+ }
+
+ /* 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)
+ return 0;
+
+ if (GetTempPath((DWORD)length, buffer))
+ return 0;
+#endif
+
+ /* This system doesn't like us, try to use the current directory */
+ if (is_valid_tmp_path(".")) {
+ strncpy(buffer, ".", length - 1);
+ buffer[length - 1] = '\0';
+ return 0;
+ }
+
+ return -1;
+}
+
+static void clar_unsandbox(void)
+{
+ if (_clar_path[0] == '\0')
+ return;
+
+ cl_must_pass(chdir(".."));
+
+ fs_rm(_clar_path);
+}
+
+static int build_sandbox_path(void)
+{
+#ifdef CLAR_TMPDIR
+ const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
+#else
+ const char path_tail[] = "clar_tmp_XXXXXX";
+#endif
+
+ size_t len;
+
+ if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
+ return -1;
+
+ len = strlen(_clar_path);
+
+#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 (_clar_path[len - 1] != '/') {
+ _clar_path[len++] = '/';
+ }
+
+ strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
+
+#if defined(__MINGW32__)
+ if (_mktemp(_clar_path) == NULL)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(_WIN32)
+ if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
+ return -1;
+
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#else
+ if (mkdtemp(_clar_path) == NULL)
+ return -1;
+#endif
+
+ return 0;
+}
+
+static int clar_sandbox(void)
+{
+ if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
+ return -1;
+
+ if (chdir(_clar_path) != 0)
+ return -1;
+
+ return 0;
+}
+
+const char *clar_sandbox_path(void)
+{
+ return _clar_path;
+}
diff --git a/t/unit-tests/clar/clar/summary.h b/t/unit-tests/clar/clar/summary.h
new file mode 100644
index 00000000000..4dd352e28b8
--- /dev/null
+++ b/t/unit-tests/clar/clar/summary.h
@@ -0,0 +1,143 @@
+
+#include <stdio.h>
+#include <time.h>
+
+static int clar_summary_close_tag(
+ struct clar_summary *summary, const char *tag, int indent)
+{
+ const char *indt;
+
+ if (indent == 0) indt = "";
+ else if (indent == 1) indt = "\t";
+ else indt = "\t\t";
+
+ return fprintf(summary->fp, "%s</%s>\n", indt, tag);
+}
+
+static int clar_summary_testsuites(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "<testsuites>\n");
+}
+
+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(×tamp);
+ char iso_dt[20];
+
+ if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
+ return -1;
+
+ return fprintf(summary->fp, "\t<testsuite"
+ " id=\"%d\""
+ " name=\"%s\""
+ " hostname=\"localhost\""
+ " timestamp=\"%s\""
+ " tests=\"%d\""
+ " failures=\"%d\""
+ " errors=\"%d\">\n",
+ idn, name, iso_dt, test_count, fail_count, error_count);
+}
+
+static int clar_summary_testcase(struct clar_summary *summary,
+ const char *name, const char *classname, double elapsed)
+{
+ return fprintf(summary->fp,
+ "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
+ name, classname, elapsed);
+}
+
+static int clar_summary_failure(struct clar_summary *summary,
+ const char *type, const char *message, const char *desc)
+{
+ return fprintf(summary->fp,
+ "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
+ type, message, desc);
+}
+
+static int clar_summary_skipped(struct clar_summary *summary)
+{
+ return fprintf(summary->fp, "\t\t\t<skipped />\n");
+}
+
+struct clar_summary *clar_summary_init(const char *filename)
+{
+ struct clar_summary *summary;
+ FILE *fp;
+
+ if ((fp = fopen(filename, "w")) == NULL) {
+ perror("fopen");
+ return NULL;
+ }
+
+ if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
+ perror("malloc");
+ fclose(fp);
+ return NULL;
+ }
+
+ summary->filename = filename;
+ summary->fp = fp;
+
+ return summary;
+}
+
+int clar_summary_shutdown(struct clar_summary *summary)
+{
+ struct clar_report *report;
+ const char *last_suite = NULL;
+
+ if (clar_summary_testsuites(summary) < 0)
+ goto on_error;
+
+ report = _clar.reports;
+ while (report != NULL) {
+ struct clar_error *error = report->errors;
+
+ if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_testsuite(summary, 0, report->suite,
+ report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
+ goto on_error;
+ }
+
+ last_suite = report->suite;
+
+ clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
+
+ while (error != NULL) {
+ if (clar_summary_failure(summary, "assert",
+ error->error_msg, error->description) < 0)
+ goto on_error;
+
+ error = error->next;
+ }
+
+ if (report->status == CL_TEST_SKIP)
+ clar_summary_skipped(summary);
+
+ if (clar_summary_close_tag(summary, "testcase", 2) < 0)
+ goto on_error;
+
+ report = report->next;
+
+ if (!report || strcmp(last_suite, report->suite) != 0) {
+ if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
+ goto on_error;
+ }
+ }
+
+ if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
+ fclose(summary->fp) != 0)
+ goto on_error;
+
+ printf("written summary file to %s\n", summary->filename);
+
+ free(summary);
+ return 0;
+
+on_error:
+ fclose(summary->fp);
+ free(summary);
+ return -1;
+}
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py
new file mode 100755
index 00000000000..80996ac3e71
--- /dev/null
+++ b/t/unit-tests/clar/generate.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+from __future__ import with_statement
+from string import Template
+import re, fnmatch, os, sys, codecs, pickle
+
+class Module(object):
+ class Template(object):
+ def __init__(self, module):
+ self.module = module
+
+ def _render_callback(self, cb):
+ if not cb:
+ return ' { NULL, NULL }'
+ return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
+
+ class DeclarationTemplate(Template):
+ def render(self):
+ out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
+
+ for initializer in self.module.initializers:
+ out += "extern %s;\n" % initializer['declaration']
+
+ if self.module.cleanup:
+ out += "extern %s;\n" % self.module.cleanup['declaration']
+
+ return out
+
+ class CallbacksTemplate(Template):
+ def render(self):
+ out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
+ out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
+ out += "\n};\n"
+ return out
+
+ class InfoTemplate(Template):
+ def render(self):
+ templates = []
+
+ initializers = self.module.initializers
+ if len(initializers) == 0:
+ initializers = [ None ]
+
+ for initializer in initializers:
+ name = self.module.clean_name()
+ if initializer and initializer['short_name'].startswith('initialize_'):
+ variant = initializer['short_name'][len('initialize_'):]
+ name += " (%s)" % variant.replace('_', ' ')
+
+ template = Template(
+ r"""
+ {
+ "${clean_name}",
+ ${initialize},
+ ${cleanup},
+ ${cb_ptr}, ${cb_count}, ${enabled}
+ }"""
+ ).substitute(
+ clean_name = name,
+ initialize = self._render_callback(initializer),
+ cleanup = self._render_callback(self.module.cleanup),
+ cb_ptr = "_clar_cb_%s" % self.module.name,
+ cb_count = len(self.module.callbacks),
+ enabled = int(self.module.enabled)
+ )
+ templates.append(template)
+
+ return ','.join(templates)
+
+ def __init__(self, name):
+ self.name = name
+
+ self.mtime = None
+ self.enabled = True
+ self.modified = False
+
+ def clean_name(self):
+ return self.name.replace("_", "::")
+
+ def _skip_comments(self, text):
+ SKIP_COMMENTS_REGEX = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE)
+
+ def _replacer(match):
+ s = match.group(0)
+ return "" if s.startswith('/') else s
+
+ return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
+
+ def parse(self, contents):
+ TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
+
+ contents = self._skip_comments(contents)
+ regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
+
+ self.callbacks = []
+ self.initializers = []
+ self.cleanup = None
+
+ for (declaration, symbol, short_name) in regex.findall(contents):
+ data = {
+ "short_name" : short_name,
+ "declaration" : declaration,
+ "symbol" : symbol
+ }
+
+ if short_name.startswith('initialize'):
+ self.initializers.append(data)
+ elif short_name == 'cleanup':
+ self.cleanup = data
+ else:
+ self.callbacks.append(data)
+
+ return self.callbacks != []
+
+ def refresh(self, path):
+ self.modified = False
+
+ try:
+ st = os.stat(path)
+
+ # Not modified
+ if st.st_mtime == self.mtime:
+ return True
+
+ self.modified = True
+ self.mtime = st.st_mtime
+
+ with codecs.open(path, encoding='utf-8') as fp:
+ raw_content = fp.read()
+
+ except IOError:
+ return False
+
+ return self.parse(raw_content)
+
+class TestSuite(object):
+
+ def __init__(self, path, output):
+ self.path = path
+ self.output = output
+
+ def should_generate(self, path):
+ if not os.path.isfile(path):
+ return True
+
+ if any(module.modified for module in self.modules.values()):
+ return True
+
+ return False
+
+ def find_modules(self):
+ modules = []
+ for root, _, files in os.walk(self.path):
+ module_root = root[len(self.path):]
+ module_root = [c for c in module_root.split(os.sep) if c]
+
+ tests_in_module = fnmatch.filter(files, "*.c")
+
+ for test_file in tests_in_module:
+ full_path = os.path.join(root, test_file)
+ module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
+
+ modules.append((full_path, module_name))
+
+ return modules
+
+ def load_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ cache = {}
+
+ try:
+ fp = open(path, 'rb')
+ cache = pickle.load(fp)
+ fp.close()
+ except (IOError, ValueError):
+ pass
+
+ return cache
+
+ def save_cache(self):
+ path = os.path.join(self.output, '.clarcache')
+ with open(path, 'wb') as cache:
+ pickle.dump(self.modules, cache)
+
+ def load(self, force = False):
+ module_data = self.find_modules()
+ self.modules = {} if force else self.load_cache()
+
+ for path, name in module_data:
+ if name not in self.modules:
+ self.modules[name] = Module(name)
+
+ if not self.modules[name].refresh(path):
+ del self.modules[name]
+
+ def disable(self, excluded):
+ for exclude in excluded:
+ for module in self.modules.values():
+ name = module.clean_name()
+ if name.startswith(exclude):
+ module.enabled = False
+ module.modified = True
+
+ def suite_count(self):
+ return sum(max(1, len(m.initializers)) for m in self.modules.values())
+
+ def callback_count(self):
+ return sum(len(module.callbacks) for module in self.modules.values())
+
+ def write(self):
+ output = os.path.join(self.output, 'clar.suite')
+
+ if not self.should_generate(output):
+ return False
+
+ with open(output, 'w') as data:
+ modules = sorted(self.modules.values(), key=lambda module: module.name)
+
+ for module in modules:
+ t = Module.DeclarationTemplate(module)
+ data.write(t.render())
+
+ for module in modules:
+ t = Module.CallbacksTemplate(module)
+ data.write(t.render())
+
+ suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
+ Module.InfoTemplate(module).render() for module in modules
+ ) + "\n};\n"
+
+ data.write(suites)
+
+ data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
+ data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
+
+ self.save_cache()
+ return True
+
+if __name__ == '__main__':
+ from optparse import OptionParser
+
+ parser = OptionParser()
+ parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
+ parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
+ parser.add_option('-o', '--output', dest='output')
+
+ options, args = parser.parse_args()
+ if len(args) > 1:
+ print("More than one path given")
+ sys.exit(1)
+
+ path = args.pop() if args else '.'
+ output = options.output or path
+ suite = TestSuite(path, output)
+ suite.load(options.force)
+ suite.disable(options.excluded)
+ if suite.write():
+ print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
diff --git a/t/unit-tests/clar/test/.gitignore b/t/unit-tests/clar/test/.gitignore
new file mode 100644
index 00000000000..a477d0c40ca
--- /dev/null
+++ b/t/unit-tests/clar/test/.gitignore
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
diff --git a/t/unit-tests/clar/test/Makefile b/t/unit-tests/clar/test/Makefile
new file mode 100644
index 00000000000..93c6b2ad32c
--- /dev/null
+++ b/t/unit-tests/clar/test/Makefile
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+#
+# Set up the path to the clar sources and to the fixtures directory
+#
+# The fixture path needs to be an absolute path so it can be used
+# even after we have chdir'ed into the test directory while testing.
+#
+CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
+TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
+CLAR_PATH := $(dir $(TEST_DIRECTORY))
+CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
+
+CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
+
+.PHONY: clean
+
+# list the objects that go into our test
+objects = main.o sample.o
+
+# build the test executable itself
+clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
+ $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
+
+# test object files depend on clar macros
+$(objects) : $(CLAR_PATH)clar.h
+
+# build the clar.suite file of test metadata
+clar.suite:
+ python "$(CLAR_PATH)generate.py" .
+
+# remove all generated files
+clean:
+ $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
diff --git a/t/unit-tests/clar/test/clar_test.h b/t/unit-tests/clar/test/clar_test.h
new file mode 100644
index 00000000000..0fcaa639aa8
--- /dev/null
+++ b/t/unit-tests/clar/test/clar_test.h
@@ -0,0 +1,16 @@
+/*
+ * 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/main.c b/t/unit-tests/clar/test/main.c
new file mode 100644
index 00000000000..59e56ad255b
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c
@@ -0,0 +1,40 @@
+/*
+ * 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_test.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;
+
+#ifdef _WIN32
+int __cdecl main(int argc, char *argv[])
+#else
+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);
+
+ /* Your custom cleanup here */
+ cl_assert_equal_i(8, global_test_counter);
+
+ return ret;
+}
diff --git a/t/unit-tests/clar/test/main.c.sample b/t/unit-tests/clar/test/main.c.sample
new file mode 100644
index 00000000000..a4d91b72fa8
--- /dev/null
+++ b/t/unit-tests/clar/test/main.c.sample
@@ -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_test.h"
+
+/*
+ * Minimal main() for clar tests.
+ *
+ * Modify this with any application specific setup or teardown that you need.
+ * 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, 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
+{
+ /* Run the test suite */
+ return clar_test(argc, argv);
+}
diff --git a/t/unit-tests/clar/test/resources/test/file b/t/unit-tests/clar/test/resources/test/file
new file mode 100644
index 00000000000..220f4aa98a7
--- /dev/null
+++ b/t/unit-tests/clar/test/resources/test/file
@@ -0,0 +1 @@
+File
diff --git a/t/unit-tests/clar/test/sample.c b/t/unit-tests/clar/test/sample.c
new file mode 100644
index 00000000000..faa1209262f
--- /dev/null
+++ b/t/unit-tests/clar/test/sample.c
@@ -0,0 +1,84 @@
+#include "clar_test.h"
+#include <sys/stat.h>
+
+static int file_size(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) == 0)
+ return (int)st.st_size;
+ return -1;
+}
+
+void test_sample__initialize(void)
+{
+ global_test_counter++;
+}
+
+void test_sample__cleanup(void)
+{
+ cl_fixture_cleanup("test");
+
+ cl_assert(file_size("test/file") == -1);
+}
+
+void test_sample__1(void)
+{
+ cl_assert(1);
+ cl_must_pass(0); /* 0 == success */
+ cl_must_fail(-1); /* <0 == failure */
+ cl_must_pass(-1); /* demonstrate a failing call */
+}
+
+void test_sample__2(void)
+{
+ cl_fixture_sandbox("test");
+
+ cl_assert(file_size("test/nonexistent") == -1);
+ cl_assert(file_size("test/file") > 0);
+ cl_assert(100 == 101);
+}
+
+void test_sample__strings(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_s("expected", actual);
+ cl_assert_equal_s_("expected", actual, "second try with annotation");
+ cl_assert_equal_s_("mismatched", actual, "this one fails");
+}
+
+void test_sample__strings_with_length(void)
+{
+ const char *actual = "expected";
+ cl_assert_equal_strn("expected_", actual, 8);
+ cl_assert_equal_strn("exactly", actual, 2);
+ cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
+ cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
+}
+
+void test_sample__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)
+{
+ int value = 100;
+ cl_assert_equal_i_fmt(022, value, "%04o");
+}
+
+void test_sample__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)
+{
+ const char *actual = "expected";
+ cl_assert_equal_p(actual, actual); /* pointers to same object */
+ cl_assert_equal_p(&actual, actual);
+}
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 03/14] t/clar: fix compatibility with NonStop
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 01/14] t: do not pass GIT_TEST_OPTS to unit tests with prove Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 02/14] t: import the clar unit testing framework Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 04/14] clar: avoid compile error with mingw-w64 Patrick Steinhardt
` (11 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The NonStop platform does not have `mkdtemp()` available, which we rely
on in `build_sandbox_path()`. Fix this issue by using `mktemp()` and
`mkdir()` instead on this platform.
This has been cherry-picked from the upstream pull request at [1].
[1]: https://github.com/clar-test/clar/pull/96
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/sandbox.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/t/unit-tests/clar/clar/sandbox.h b/t/unit-tests/clar/clar/sandbox.h
index 7c177f35258..e25057b7c49 100644
--- a/t/unit-tests/clar/clar/sandbox.h
+++ b/t/unit-tests/clar/clar/sandbox.h
@@ -120,6 +120,12 @@ static int build_sandbox_path(void)
if (_mktemp(_clar_path) == NULL)
return -1;
+ if (mkdir(_clar_path, 0700) != 0)
+ return -1;
+#elif defined(__TANDEM)
+ if (mktemp(_clar_path) == NULL)
+ return -1;
+
if (mkdir(_clar_path, 0700) != 0)
return -1;
#elif defined(_WIN32)
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 04/14] clar: avoid compile error with mingw-w64
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (2 preceding siblings ...)
2024-09-04 14:16 ` [PATCH v8 03/14] t/clar: fix compatibility with NonStop Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 05/14] clar(win32): avoid compile error due to unused `fs_copy()` Patrick Steinhardt
` (10 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When using mingw-w64 to compile the code, and using `_stat()`, it is
necessary to use `struct _stat`, too, and not `struct stat` (as the
latter is incompatible with the "dashed" version because it is limited
to 32-bit time types for backwards compatibility).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index 3fc2c768158..e2ebe551d38 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -68,7 +68,7 @@
# define PRIxZ "Ix"
# endif
-# if defined(_MSC_VER) || defined(__MINGW32__)
+# if defined(_MSC_VER) || (defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
typedef struct stat STAT_T;
# else
typedef struct _stat STAT_T;
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 05/14] clar(win32): avoid compile error due to unused `fs_copy()`
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (3 preceding siblings ...)
2024-09-04 14:16 ` [PATCH v8 04/14] clar: avoid compile error with mingw-w64 Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:16 ` [PATCH v8 06/14] clar: stop including `shellapi.h` unnecessarily Patrick Steinhardt
` (9 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
When CLAR_FIXTURE_PATH is unset, the `fs_copy()` function seems not to
be used. But it is declared as `static`, and GCC does not like that,
complaining that it should not be declared/defined to begin with.
We could mark this function as (potentially) unused by following the
`MAYBE_UNUSED` pattern from Git's `git-compat-util.h`. However, this is
a GCC-only construct that is not understood by Visual C. Besides, `clar`
does not use that pattern at all.
Instead, let's use the `((void)SYMBOL);` pattern that `clar` already
uses elsewhere; This avoids the compile error by sorta kinda make the
function used after a fashion.
Note: GCC 14.x (which Git for Windows' SDK already uses) is able to
figure out that this function is unused even though there are recursive
calls between `fs_copy()` and `fs_copydir_helper()`; Earlier GCC
versions do not detect that, and therefore the issue has been hidden
from the regular Linux CI builds (where GCC 14.x is not yet used). That
is the reason why this change is only made in the Windows-specific
portion of `t/unit-tests/clar/clar/fs.h`.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar/fs.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h
index 3e39890bd3e..8b206179fc4 100644
--- a/t/unit-tests/clar/clar/fs.h
+++ b/t/unit-tests/clar/clar/fs.h
@@ -297,6 +297,8 @@ cl_fs_cleanup(void)
{
#ifdef CLAR_FIXTURE_PATH
fs_rm(fixture_path(_clar_path, "*"));
+#else
+ ((void)fs_copy); /* unused */
#endif
}
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 06/14] clar: stop including `shellapi.h` unnecessarily
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (4 preceding siblings ...)
2024-09-04 14:16 ` [PATCH v8 05/14] clar(win32): avoid compile error due to unused `fs_copy()` Patrick Steinhardt
@ 2024-09-04 14:16 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 07/14] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
` (8 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:16 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
The `shellapi.h` header was included as of
https://github.com/clar-test/clar/commit/136e763211aa, to have
`SHFileOperation()` declared so that it could be called.
However, https://github.com/clar-test/clar/commit/5ce31b69b525 removed
that call, and therefore that `#include <shellapi.h>` is unnecessary.
It is also unwanted in Git because this project uses a subset of Git for
Windows' SDK in its CI builds that (for bandwidth reasons) excludes tons
of header files, including `shellapi.h`.
So let's remove it.
Note: Since the `windows.h` header would include `shellapi.h` anyway, we
also define `WIN32_LEAN_AND_MEAN` to avoid this and similar other
unnecessary includes before including `windows.h`.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/clar/clar.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c
index e2ebe551d38..cef0f023c24 100644
--- a/t/unit-tests/clar/clar.c
+++ b/t/unit-tests/clar/clar.c
@@ -19,9 +19,9 @@
#include <sys/stat.h>
#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <io.h>
-# include <shellapi.h>
# include <direct.h>
# define _MAIN_CC __cdecl
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 07/14] Makefile: fix sparse dependency on GENERATED_H
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (5 preceding siblings ...)
2024-09-04 14:16 ` [PATCH v8 06/14] clar: stop including `shellapi.h` unnecessarily Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 08/14] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
` (7 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The "check" Makefile target is essentially an alias around the "sparse"
target. The one difference though is that it will tell users to instead
run the "test" target in case they do not have sparse(1) installed, as
chances are high that they wanted to execute the test suite rather than
doing semantic checks.
But even though the "check" target ultimately just ends up executing
`make sparse`, it still depends on our generated headers. This does not
make any sense though: they are irrelevant for the "test" target advice,
and if these headers are required for the "sparse" target they must be
declared as a dependency on the aliased target, not the alias.
But even moving the dependency to the "sparse" target is wrong, as
concurrent builds may then end up generating the headers and running
sparse concurrently. Instead, we make them a dependency of the specific
objects. While that is overly broad, it does ensure correct ordering.
The alternative, specifying which file depends on what generated header
explicitly, feels rather unmaintainable.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
index 28742a60964..efd305ab358 100644
--- a/Makefile
+++ b/Makefile
@@ -3254,7 +3254,7 @@ check-sha1:: t/helper/test-tool$X
SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
-$(SP_OBJ): %.sp: %.c %.o
+$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
-Wsparse-error \
$(SPARSE_FLAGS) $(SP_EXTRA_FLAGS) $< && \
@@ -3295,7 +3295,7 @@ style:
git clang-format --style file --diff --extensions c,h
.PHONY: check
-check: $(GENERATED_H)
+check:
@if sparse; \
then \
echo >&2 "Use 'make sparse' instead"; \
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 08/14] Makefile: make hdr-check depend on generated headers
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (6 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 07/14] Makefile: fix sparse dependency on GENERATED_H Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 09/14] Makefile: do not use sparse on third-party sources Patrick Steinhardt
` (6 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The "hdr-check" Makefile target compiles each of our headers as a
standalone code unit to ensure that they are not missing any type
declarations and can be included standalone.
With the next commit we will wire up the clar unit testing framework,
which will have the effect that some headers start depending on
generated ones. While we could declare that dependency explicitly, it
does not really feel very maintainable in the future.
Instead, we do the same as in the preceding commit and have the objects
depend on all of our generated headers. While again overly broad, it is
easy to maintain and generating headers is not an expensive thing to do
anyway.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index efd305ab358..8c4487dd0c6 100644
--- a/Makefile
+++ b/Makefile
@@ -3284,7 +3284,7 @@ HCC = $(HCO:hco=hcc)
@echo '#include "git-compat-util.h"' >$@
@echo '#include "$<"' >>$@
-$(HCO): %.hco: %.hcc FORCE
+$(HCO): %.hco: %.hcc $(GENERATED_H) FORCE
$(QUIET_HDR)$(CC) $(ALL_CFLAGS) -o /dev/null -c -xc $<
.PHONY: hdr-check $(HCO)
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 09/14] Makefile: do not use sparse on third-party sources
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (7 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 08/14] Makefile: make hdr-check depend on generated headers Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 10/14] Makefile: wire up the clar unit testing framework Patrick Steinhardt
` (5 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
We have several third-party sources in our codebase that we have
imported from upstream projects. These sources are mostly excluded from
our static analysis, for example when running Coccinelle.
Do the same for our "sparse" target by filtering them out.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 8c4487dd0c6..81a47b61327 100644
--- a/Makefile
+++ b/Makefile
@@ -3252,7 +3252,8 @@ t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
check-sha1:: t/helper/test-tool$X
t/helper/test-sha1.sh
-SP_OBJ = $(patsubst %.o,%.sp,$(OBJECTS))
+SP_SRC = $(filter-out $(THIRD_PARTY_SOURCES),$(patsubst %.o,%.c,$(OBJECTS)))
+SP_OBJ = $(patsubst %.c,%.sp,$(SP_SRC))
$(SP_OBJ): %.sp: %.c %.o $(GENERATED_H)
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) \
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 10/14] Makefile: wire up the clar unit testing framework
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (8 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 09/14] Makefile: do not use sparse on third-party sources Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-09 18:17 ` Junio C Hamano
2024-09-04 14:17 ` [PATCH v8 11/14] t/unit-tests: implement test driver Patrick Steinhardt
` (4 subsequent siblings)
14 siblings, 1 reply; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Wire up the clar unit testing framework by introducing a new
"unit-tests" executable. In contrast to the existing framework, this
will result in a single executable for all test suites. The ability to
pick specific tests to execute is retained via functionality built into
the clar itself.
Note that we need to be a bit careful about how we need to invalidate
our Makefile rules. While we obviously have to regenerate the clar suite
when our test suites change, we also have to invalidate it in case any
of the test suites gets removed. We do so by using our typical pattern
of creating a `GIT-TEST-SUITES` file that gets updated whenever the set
of test suites changes, so that we can easily depend on that file.
Another specialty is that we generate a "clar-decls.h" file. The test
functions are neither static, nor do they have external declarations.
This is because they are getting parsed via "generate.py", which then
creates the external generations that get populated into an array. These
declarations are only seen by the main function though.
The consequence is that we will get a bunch of "missing prototypes"
errors from our compiler for each of these test functions. To fix those
errors, we extract the `extern` declarations from "clar.suite" and put
them into a standalone header that then gets included by each of our
unit tests. This gets rid of compiler warnings for every function which
has been extracted by "generate.py". More importantly though, it does
_not_ get rid of warnings in case a function really isn't being used by
anything. Thus, it would cause a compiler error if a function name was
mistyped and thus not picked up by "generate.py".
The test driver "unit-test.c" is an empty stub for now. It will get
implemented in the next commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
.gitignore | 1 +
Makefile | 36 +++++++++++++++++++++---
t/Makefile | 1 +
t/unit-tests/.gitignore | 2 ++
t/unit-tests/clar-generate.awk | 50 ++++++++++++++++++++++++++++++++++
t/unit-tests/unit-test.c | 6 ++++
t/unit-tests/unit-test.h | 3 ++
7 files changed, 95 insertions(+), 4 deletions(-)
create mode 100644 t/unit-tests/clar-generate.awk
create mode 100644 t/unit-tests/unit-test.c
create mode 100644 t/unit-tests/unit-test.h
diff --git a/.gitignore b/.gitignore
index 8caf3700c23..6687bd6db4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/GIT-PYTHON-VARS
/GIT-SCRIPT-DEFINES
/GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
/GIT-USER-AGENT
/GIT-VERSION-FILE
/bin-wrappers/
diff --git a/Makefile b/Makefile
index 81a47b61327..e38146b5eb0 100644
--- a/Makefile
+++ b/Makefile
@@ -914,6 +914,8 @@ REFTABLE_TEST_LIB = reftable/libreftable_test.a
GENERATED_H += command-list.h
GENERATED_H += config-list.h
GENERATED_H += hook-list.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar-decls.h
+GENERATED_H += $(UNIT_TEST_DIR)/clar.suite
.PHONY: generated-hdrs
generated-hdrs: $(GENERATED_H)
@@ -1334,6 +1336,11 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
+UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
+UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
+
UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
@@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS)
OBJECTS += $(FUZZ_OBJS)
OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
OBJECTS += $(UNIT_TEST_OBJS)
+OBJECTS += $(UNIT_TESTS_OBJS)
ifndef NO_CURL
OBJECTS += http.o http-walker.o remote-curl.o
@@ -3216,7 +3224,7 @@ endif
test_bindir_programs := $(patsubst %,bin-wrappers/%,$(BINDIR_PROGRAMS_NEED_X) $(BINDIR_PROGRAMS_NO_X) $(TEST_PROGRAMS_NEED_X))
-all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS)
+all:: $(TEST_PROGRAMS) $(test_bindir_programs) $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
bin-wrappers/%: wrap-for-bin.sh
$(call mkdir_p_parent_template)
@@ -3648,7 +3656,7 @@ endif
artifacts-tar:: $(ALL_COMMANDS_TO_INSTALL) $(SCRIPT_LIB) $(OTHER_PROGRAMS) \
GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
- $(UNIT_TEST_PROGS) $(MOFILES)
+ $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) $(MOFILES)
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) \
SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)'
test -n "$(ARTIFACTS_DIRECTORY)"
@@ -3704,6 +3712,7 @@ cocciclean:
clean: profile-clean coverage-clean cocciclean
$(RM) -r .build $(UNIT_TEST_BIN)
+ $(RM) GIT-TEST-SUITES
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
@@ -3863,7 +3872,26 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o \
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
$(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+GIT-TEST-SUITES: FORCE
+ @FLAGS='$(UNIT_TESTS_SUITES)'; \
+ if test x"$$FLAGS" != x"`cat GIT-TEST-SUITES 2>/dev/null`" ; then \
+ echo >&2 " * new test suites"; \
+ echo "$$FLAGS" >GIT-TEST-SUITES; \
+ fi
+
+$(UNIT_TEST_DIR)/clar-decls.h: $(patsubst %,$(UNIT_TEST_DIR)/%.c,$(UNIT_TESTS_SUITES)) GIT-TEST-SUITES
+ $(QUIET_GEN)for suite in $(UNIT_TESTS_SUITES); do \
+ sed -ne "s/^\(void test_$${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*(void)$$\)/extern \1;/p" $(UNIT_TEST_DIR)/$$suite.c; \
+ done >$@
+$(UNIT_TEST_DIR)/clar.suite: $(UNIT_TEST_DIR)/clar-decls.h
+ $(QUIET_GEN)awk -f $(UNIT_TEST_DIR)/clar-generate.awk $< >$(UNIT_TEST_DIR)/clar.suite
+$(UNIT_TESTS_OBJS): $(UNIT_TEST_DIR)/clar-decls.h
+$(UNIT_TESTS_OBJS): EXTRA_CPPFLAGS = -I$(UNIT_TEST_DIR)
+$(UNIT_TESTS_PROG): $(UNIT_TEST_DIR)/clar.suite $(UNIT_TESTS_OBJS) $(GITLIBS) GIT-LDFLAGS
+ $(call mkdir_p_parent_template)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
.PHONY: build-unit-tests unit-tests
-build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
+build-unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG)
+unit-tests: $(UNIT_TEST_PROGS) $(UNIT_TESTS_PROG) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/t/Makefile b/t/Makefile
index d2212de0b78..131ffd778fe 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,6 +48,7 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
+UNIT_TEST_PROGRAMS += unit-tests/bin/unit-tests$(X)
UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore
index 5e56e040ec0..d0632ec7f9e 100644
--- a/t/unit-tests/.gitignore
+++ b/t/unit-tests/.gitignore
@@ -1 +1,3 @@
/bin
+/clar.suite
+/clar-decls.h
diff --git a/t/unit-tests/clar-generate.awk b/t/unit-tests/clar-generate.awk
new file mode 100644
index 00000000000..ab71ce6c9fc
--- /dev/null
+++ b/t/unit-tests/clar-generate.awk
@@ -0,0 +1,50 @@
+function add_suite(suite, initialize, cleanup, count) {
+ if (!suite) return
+ suite_count++
+ callback_count += count
+ suites = suites " {\n"
+ suites = suites " \"" suite "\",\n"
+ suites = suites " " initialize ",\n"
+ suites = suites " " cleanup ",\n"
+ suites = suites " _clar_cb_" suite ", " count ", 1\n"
+ suites = suites " },\n"
+}
+
+BEGIN {
+ suites = "static struct clar_suite _clar_suites[] = {\n"
+}
+
+{
+ print
+ name = $3; sub(/\(.*$/, "", name)
+ suite = name; sub(/^test_/, "", suite); sub(/__.*$/, "", suite)
+ short_name = name; sub(/^.*__/, "", short_name)
+ cb = "{ \"" short_name "\", &" name " }"
+ if (suite != prev_suite) {
+ add_suite(prev_suite, initialize, cleanup, count)
+ if (callbacks) callbacks = callbacks "};\n"
+ callbacks = callbacks "static const struct clar_func _clar_cb_" suite "[] = {\n"
+ initialize = "{ NULL, NULL }"
+ cleanup = "{ NULL, NULL }"
+ count = 0
+ prev_suite = suite
+ }
+ if (short_name == "initialize") {
+ initialize = cb
+ } else if (short_name == "cleanup") {
+ cleanup = cb
+ } else {
+ callbacks = callbacks " " cb ",\n"
+ count++
+ }
+}
+
+END {
+ add_suite(suite, initialize, cleanup, count)
+ suites = suites "};"
+ if (callbacks) callbacks = callbacks "};"
+ print callbacks
+ print suites
+ print "static const size_t _clar_suite_count = " suite_count ";"
+ print "static const size_t _clar_callback_count = " callback_count ";"
+}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
new file mode 100644
index 00000000000..3d12cde6dae
--- /dev/null
+++ b/t/unit-tests/unit-test.c
@@ -0,0 +1,6 @@
+#include "unit-test.h"
+
+int cmd_main(int argc UNUSED, const char **argv UNUSED)
+{
+ return 0;
+}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
new file mode 100644
index 00000000000..66ec2387cc6
--- /dev/null
+++ b/t/unit-tests/unit-test.h
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v8 10/14] Makefile: wire up the clar unit testing framework
2024-09-04 14:17 ` [PATCH v8 10/14] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-09-09 18:17 ` Junio C Hamano
2024-09-10 6:20 ` Patrick Steinhardt
0 siblings, 1 reply; 172+ messages in thread
From: Junio C Hamano @ 2024-09-09 18:17 UTC (permalink / raw)
To: Patrick Steinhardt
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Patrick Steinhardt <ps@pks.im> writes:
> +UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
> +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
> +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
> +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
> +
> UNIT_TEST_PROGRAMS += t-ctype
> UNIT_TEST_PROGRAMS += t-example-decorate
> UNIT_TEST_PROGRAMS += t-hash
> @@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS)
> OBJECTS += $(FUZZ_OBJS)
> OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
> OBJECTS += $(UNIT_TEST_OBJS)
> +OBJECTS += $(UNIT_TESTS_OBJS)
What is the longer term direction we envision? Do we aim to retire
the UNIT_TEST_* and end up with only the clar based tests?
At least until that happens, can we have "UNIT_TESTS" -> "CLAR_TEST"
or something that makes it less confusing? Every time I see merge
conflicts around this area in the Makefile, I get puzzled and wonder
which one is which.
Thanks.
^ permalink raw reply [flat|nested] 172+ messages in thread
* Re: [PATCH v8 10/14] Makefile: wire up the clar unit testing framework
2024-09-09 18:17 ` Junio C Hamano
@ 2024-09-10 6:20 ` Patrick Steinhardt
0 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-10 6:20 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, René Scharfe, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
On Mon, Sep 09, 2024 at 11:17:43AM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > +UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
> > +UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
> > +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
> > +UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
> > +
> > UNIT_TEST_PROGRAMS += t-ctype
> > UNIT_TEST_PROGRAMS += t-example-decorate
> > UNIT_TEST_PROGRAMS += t-hash
> > @@ -2714,6 +2721,7 @@ OBJECTS += $(XDIFF_OBJS)
> > OBJECTS += $(FUZZ_OBJS)
> > OBJECTS += $(REFTABLE_OBJS) $(REFTABLE_TEST_OBJS)
> > OBJECTS += $(UNIT_TEST_OBJS)
> > +OBJECTS += $(UNIT_TESTS_OBJS)
>
> What is the longer term direction we envision? Do we aim to retire
> the UNIT_TEST_* and end up with only the clar based tests?
Yeah, that's my plan. I first want to let the new clar-based tests cook
a bit to shake out any issues with it, and address issues with clar that
were raised during review upstream. But once that's done and we see that
it works alright for our purpose I'd aim to convert all test suites to
use the clar such that we ultimately end up with a single unit testing
framework, only.
> At least until that happens, can we have "UNIT_TESTS" -> "CLAR_TEST"
> or something that makes it less confusing? Every time I see merge
> conflicts around this area in the Makefile, I get puzzled and wonder
> which one is which.
These are indeed quite similarly named, so I see where the confusion
comes from. I'll send a patch in a bit to rename them.
Patrick
^ permalink raw reply [flat|nested] 172+ messages in thread
* [PATCH v8 11/14] t/unit-tests: implement test driver
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (9 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 10/14] Makefile: wire up the clar unit testing framework Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 12/14] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
` (3 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
The test driver in "unit-test.c" is responsible for setting up our unit
tests and eventually running them. As such, it is also responsible for
parsing the command line arguments.
The clar unit testing framework provides function `clar_test()` that
parses command line arguments and then executes the tests for us. In
theory that would already be sufficient. We have the special requirement
to always generate TAP-formatted output though, so we'd have to always
pass the "-t" argument to clar. Furthermore, some of the options exposed
by clar are ineffective when "-t" is used, but they would still be shown
when the user passes the "-h" parameter to have the clar show its usage.
Implement our own option handling instead of using the one provided by
clar, which gives us greater flexibility in how exactly we set things
up.
We would ideally not use any "normal" code of ours for this such that
the unit testing framework doesn't depend on it working correctly. But
it is somewhat dubious whether we really want to reimplement all of the
option parsing. So for now, let's be pragmatic and reuse it until we
find a good reason in the future why we'd really want to avoid it.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
t/unit-tests/unit-test.c | 45 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 43 insertions(+), 2 deletions(-)
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c
index 3d12cde6dae..a474cdcfd35 100644
--- a/t/unit-tests/unit-test.c
+++ b/t/unit-tests/unit-test.c
@@ -1,6 +1,47 @@
#include "unit-test.h"
+#include "parse-options.h"
+#include "string-list.h"
+#include "strvec.h"
-int cmd_main(int argc UNUSED, const char **argv UNUSED)
+static const char * const unit_test_usage[] = {
+ N_("unit-test [<options>]"),
+ NULL,
+};
+
+int cmd_main(int argc, const char **argv)
{
- return 0;
+ struct string_list run_args = STRING_LIST_INIT_NODUP;
+ struct string_list exclude_args = STRING_LIST_INIT_NODUP;
+ int immediate = 0;
+ struct option options[] = {
+ OPT_BOOL('i', "immediate", &immediate,
+ N_("immediately exit upon the first failed test")),
+ OPT_STRING_LIST('r', "run", &run_args, N_("suite[::test]"),
+ N_("run only test suite or individual test <suite[::test]>")),
+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("suite"),
+ N_("exclude test suite <suite>")),
+ OPT_END(),
+ };
+ struct strvec args = STRVEC_INIT;
+ int ret;
+
+ argc = parse_options(argc, argv, NULL, options,
+ unit_test_usage, PARSE_OPT_KEEP_ARGV0);
+ if (argc > 1)
+ usagef(_("extra command line parameter '%s'"), argv[0]);
+
+ strvec_push(&args, argv[0]);
+ strvec_push(&args, "-t");
+ if (immediate)
+ strvec_push(&args, "-Q");
+ for (size_t i = 0; i < run_args.nr; i++)
+ strvec_pushf(&args, "-s%s", run_args.items[i].string);
+ for (size_t i = 0; i < exclude_args.nr; i++)
+ strvec_pushf(&args, "-x%s", exclude_args.items[i].string);
+
+ ret = clar_test(args.nr, (char **) args.v);
+
+ string_list_clear(&run_args, 0);
+ strvec_clear(&args);
+ return ret;
}
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 12/14] t/unit-tests: convert strvec tests to use clar
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (10 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 11/14] t/unit-tests: implement test driver Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 13/14] t/unit-tests: convert ctype " Patrick Steinhardt
` (2 subsequent siblings)
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Convert the strvec tests to use the new clar unit testing framework.
This is a first test balloon that demonstrates how the testing infra for
clar-based tests looks like.
The tests are part of the "t/unit-tests/bin/unit-tests" binary. When
running that binary with an injected error, it generates TAP output:
# ./t/unit-tests/bin/unit-tests
TAP version 13
# start of suite 1: strvec
ok 1 - strvec::init
ok 2 - strvec::dynamic_init
ok 3 - strvec::clear
not ok 4 - strvec::push
---
reason: |
String mismatch: (&vec)->v[i] != expect[i]
'foo' != 'fo' (at byte 2)
at:
file: 't/unit-tests/strvec.c'
line: 48
function: 'test_strvec__push'
---
ok 5 - strvec::pushf
ok 6 - strvec::pushl
ok 7 - strvec::pushv
ok 8 - strvec::replace_at_head
ok 9 - strvec::replace_at_tail
ok 10 - strvec::replace_in_between
ok 11 - strvec::replace_with_substring
ok 12 - strvec::remove_at_head
ok 13 - strvec::remove_at_tail
ok 14 - strvec::remove_in_between
ok 15 - strvec::pop_empty_array
ok 16 - strvec::pop_non_empty_array
ok 17 - strvec::split_empty_string
ok 18 - strvec::split_single_item
ok 19 - strvec::split_multiple_items
ok 20 - strvec::split_whitespace_only
ok 21 - strvec::split_multiple_consecutive_whitespaces
ok 22 - strvec::detach
1..22
The binary also supports some parameters that allow us to run only a
subset of unit tests or alter the output:
$ ./t/unit-tests/bin/unit-tests -h
Usage: ./t/unit-tests/bin/unit-tests [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 Only report tests that had an error
-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)
Furthermore, running `make unit-tests` runs the binary along with all
the other unit tests we have.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/strvec.c | 241 ++++++++++++++++++++++++++++++++++++++++
t/unit-tests/t-strvec.c | 211 -----------------------------------
3 files changed, 242 insertions(+), 212 deletions(-)
create mode 100644 t/unit-tests/strvec.c
delete mode 100644 t/unit-tests/t-strvec.c
diff --git a/Makefile b/Makefile
index e38146b5eb0..56ce6c00e44 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
@@ -1356,7 +1357,6 @@ UNIT_TEST_PROGRAMS += t-reftable-record
UNIT_TEST_PROGRAMS += t-reftable-tree
UNIT_TEST_PROGRAMS += t-strbuf
UNIT_TEST_PROGRAMS += t-strcmp-offset
-UNIT_TEST_PROGRAMS += t-strvec
UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
diff --git a/t/unit-tests/strvec.c b/t/unit-tests/strvec.c
new file mode 100644
index 00000000000..bf4c0cb172e
--- /dev/null
+++ b/t/unit-tests/strvec.c
@@ -0,0 +1,241 @@
+#include "unit-test.h"
+#include "strbuf.h"
+#include "strvec.h"
+
+#define check_strvec(vec, ...) \
+ do { \
+ const char *expect[] = { __VA_ARGS__ }; \
+ size_t expect_len = ARRAY_SIZE(expect); \
+ cl_assert(expect_len > 0); \
+ cl_assert_equal_p(expect[expect_len - 1], NULL); \
+ cl_assert_equal_i((vec)->nr, expect_len - 1); \
+ cl_assert((vec)->nr <= (vec)->alloc); \
+ for (size_t i = 0; i < expect_len; i++) \
+ cl_assert_equal_s((vec)->v[i], expect[i]); \
+ } while (0)
+
+void test_strvec__init(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__dynamic_init(void)
+{
+ struct strvec vec;
+
+ strvec_init(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__clear(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_push(&vec, "foo");
+ strvec_clear(&vec);
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+}
+
+void test_strvec__push(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_push(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+
+ strvec_push(&vec, "bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushf(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushf(&vec, "foo: %d", 1);
+ check_strvec(&vec, "foo: 1", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushl(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pushv(void)
+{
+ const char *strings[] = {
+ "foo", "bar", "baz", NULL,
+ };
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushv(&vec, strings);
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 0, "replaced");
+ check_strvec(&vec, "replaced", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 2, "replaced");
+ check_strvec(&vec, "foo", "bar", "replaced", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_replace(&vec, 1, "replaced");
+ check_strvec(&vec, "foo", "replaced", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__replace_with_substring(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", NULL);
+ strvec_replace(&vec, 0, vec.v[0] + 1);
+ check_strvec(&vec, "oo", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_at_head(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 0);
+ check_strvec(&vec, "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_at_tail(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 2);
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__remove_in_between(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_remove(&vec, 1);
+ check_strvec(&vec, "foo", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pop_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pop(&vec);
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__pop_non_empty_array(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
+ strvec_pop(&vec);
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_empty_string(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_single_item(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "foo");
+ check_strvec(&vec, "foo", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_multiple_items(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "foo bar baz");
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_whitespace_only(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, " \t\n");
+ check_strvec(&vec, NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__split_multiple_consecutive_whitespaces(void)
+{
+ struct strvec vec = STRVEC_INIT;
+
+ strvec_split(&vec, "foo\n\t bar");
+ check_strvec(&vec, "foo", "bar", NULL);
+ strvec_clear(&vec);
+}
+
+void test_strvec__detach(void)
+{
+ struct strvec vec = STRVEC_INIT;
+ const char **detached;
+
+ strvec_push(&vec, "foo");
+
+ detached = strvec_detach(&vec);
+ cl_assert_equal_s(detached[0], "foo");
+ cl_assert_equal_p(detached[1], NULL);
+
+ cl_assert_equal_p(vec.v, empty_strvec);
+ cl_assert_equal_i(vec.nr, 0);
+ cl_assert_equal_i(vec.alloc, 0);
+
+ free((char *) detached[0]);
+ free(detached);
+}
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
deleted file mode 100644
index c4bac8fc91b..00000000000
--- a/t/unit-tests/t-strvec.c
+++ /dev/null
@@ -1,211 +0,0 @@
-#include "test-lib.h"
-#include "strbuf.h"
-#include "strvec.h"
-
-#define check_strvec(vec, ...) \
- do { \
- const char *expect[] = { __VA_ARGS__ }; \
- if (check_uint(ARRAY_SIZE(expect), >, 0) && \
- check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
- check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
- check_uint((vec)->nr, <=, (vec)->alloc)) { \
- for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
- if (!check_str((vec)->v[i], expect[i])) { \
- test_msg(" i: %"PRIuMAX, \
- (uintmax_t)i); \
- break; \
- } \
- } \
- } \
- } while (0)
-
-int cmd_main(int argc, const char **argv)
-{
- if_test ("static initialization") {
- struct strvec vec = STRVEC_INIT;
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("dynamic initialization") {
- struct strvec vec;
- strvec_init(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("clear") {
- struct strvec vec = STRVEC_INIT;
- strvec_push(&vec, "foo");
- strvec_clear(&vec);
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
- }
-
- if_test ("push") {
- struct strvec vec = STRVEC_INIT;
-
- strvec_push(&vec, "foo");
- check_strvec(&vec, "foo", NULL);
-
- strvec_push(&vec, "bar");
- check_strvec(&vec, "foo", "bar", NULL);
-
- strvec_clear(&vec);
- }
-
- if_test ("pushf") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushf(&vec, "foo: %d", 1);
- check_strvec(&vec, "foo: 1", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pushl") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- check_strvec(&vec, "foo", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pushv") {
- const char *strings[] = {
- "foo", "bar", "baz", NULL,
- };
- struct strvec vec = STRVEC_INIT;
-
- strvec_pushv(&vec, strings);
- check_strvec(&vec, "foo", "bar", "baz", NULL);
-
- strvec_clear(&vec);
- }
-
- if_test ("replace at head") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 0, "replaced");
- check_strvec(&vec, "replaced", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace at tail") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 2, "replaced");
- check_strvec(&vec, "foo", "bar", "replaced", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace in between") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_replace(&vec, 1, "replaced");
- check_strvec(&vec, "foo", "replaced", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("replace with substring") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", NULL);
- strvec_replace(&vec, 0, vec.v[0] + 1);
- check_strvec(&vec, "oo", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove at head") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 0);
- check_strvec(&vec, "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove at tail") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 2);
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("remove in between") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_remove(&vec, 1);
- check_strvec(&vec, "foo", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pop with empty array") {
- struct strvec vec = STRVEC_INIT;
- strvec_pop(&vec);
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("pop with non-empty array") {
- struct strvec vec = STRVEC_INIT;
- strvec_pushl(&vec, "foo", "bar", "baz", NULL);
- strvec_pop(&vec);
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split empty string") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "");
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split single item") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo");
- check_strvec(&vec, "foo", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split multiple items") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo bar baz");
- check_strvec(&vec, "foo", "bar", "baz", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split whitespace only") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, " \t\n");
- check_strvec(&vec, NULL);
- strvec_clear(&vec);
- }
-
- if_test ("split multiple consecutive whitespaces") {
- struct strvec vec = STRVEC_INIT;
- strvec_split(&vec, "foo\n\t bar");
- check_strvec(&vec, "foo", "bar", NULL);
- strvec_clear(&vec);
- }
-
- if_test ("detach") {
- struct strvec vec = STRVEC_INIT;
- const char **detached;
-
- strvec_push(&vec, "foo");
-
- detached = strvec_detach(&vec);
- check_str(detached[0], "foo");
- check_pointer_eq(detached[1], NULL);
-
- check_pointer_eq(vec.v, empty_strvec);
- check_uint(vec.nr, ==, 0);
- check_uint(vec.alloc, ==, 0);
-
- free((char *) detached[0]);
- free(detached);
- }
-
- return test_done();
-}
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 13/14] t/unit-tests: convert ctype tests to use clar
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (11 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 12/14] t/unit-tests: convert strvec tests to use clar Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:17 ` [PATCH v8 14/14] clar: add CMake support Patrick Steinhardt
2024-09-04 14:32 ` [PATCH v8 00/14] Introduce clar testing framework phillip.wood123
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Convert the ctype tests to use the new clar unit testing framework.
Introduce a new function `cl_failf()` that allows us to print a
formatted error message, which we can use to point out which of the
characters was classified incorrectly. This results in output like this
on failure:
# start of suite 1: ctype
not ok 1 - ctype::isspace
---
reason: |
Test failed.
0x0d is classified incorrectly: expected 0, got 1
at:
file: 't/unit-tests/ctype.c'
line: 36
function: 'test_ctype__isspace'
---
ok 2 - ctype::isdigit
ok 3 - ctype::isalpha
ok 4 - ctype::isalnum
ok 5 - ctype::is_glob_special
ok 6 - ctype::is_regex_special
ok 7 - ctype::is_pathspec_magic
ok 8 - ctype::isascii
ok 9 - ctype::islower
ok 10 - ctype::isupper
ok 11 - ctype::iscntrl
ok 12 - ctype::ispunct
ok 13 - ctype::isxdigit
ok 14 - ctype::isprint
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
Makefile | 2 +-
t/unit-tests/{t-ctype.c => ctype.c} | 71 +++++++++++++++++++++++++----
t/unit-tests/unit-test.h | 7 +++
3 files changed, 69 insertions(+), 11 deletions(-)
rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
diff --git a/Makefile b/Makefile
index 56ce6c00e44..c841cf70063 100644
--- a/Makefile
+++ b/Makefile
@@ -1336,13 +1336,13 @@ THIRD_PARTY_SOURCES += sha1dc/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
+UNIT_TESTS_SUITES += ctype
UNIT_TESTS_SUITES += strvec
UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$(X)
UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o
UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o
-UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGRAMS += t-example-decorate
UNIT_TEST_PROGRAMS += t-hash
UNIT_TEST_PROGRAMS += t-hashmap
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c
similarity index 68%
rename from t/unit-tests/t-ctype.c
rename to t/unit-tests/ctype.c
index e28a7f50f9a..32e65867cdc 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/ctype.c
@@ -1,16 +1,16 @@
-#include "test-lib.h"
+#include "unit-test.h"
#define TEST_CHAR_CLASS(class, string) do { \
size_t len = ARRAY_SIZE(string) - 1 + \
BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \
BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \
- if_test (#class " works") { \
- for (int i = 0; i < 256; i++) { \
- if (!check_int(class(i), ==, !!memchr(string, i, len)))\
- test_msg(" i: 0x%02x", i); \
- } \
- check(!class(EOF)); \
+ for (int i = 0; i < 256; i++) { \
+ int actual = class(i), expect = !!memchr(string, i, len); \
+ if (actual != expect) \
+ cl_failf("0x%02x is classified incorrectly: expected %d, got %d", \
+ i, expect, actual); \
} \
+ cl_assert(!class(EOF)); \
} while (0)
#define DIGIT "0123456789"
@@ -31,21 +31,72 @@
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
"\x7f"
-int cmd_main(int argc, const char **argv) {
+void test_ctype__isspace(void)
+{
TEST_CHAR_CLASS(isspace, " \n\r\t");
+}
+
+void test_ctype__isdigit(void)
+{
TEST_CHAR_CLASS(isdigit, DIGIT);
+}
+
+void test_ctype__isalpha(void)
+{
TEST_CHAR_CLASS(isalpha, LOWER UPPER);
+}
+
+void test_ctype__isalnum(void)
+{
TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);
+}
+
+void test_ctype__is_glob_special(void)
+{
TEST_CHAR_CLASS(is_glob_special, "*?[\\");
+}
+
+void test_ctype__is_regex_special(void)
+{
TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|");
+}
+
+void test_ctype__is_pathspec_magic(void)
+{
TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
+}
+
+void test_ctype__isascii(void)
+{
TEST_CHAR_CLASS(isascii, ASCII);
+}
+
+void test_ctype__islower(void)
+{
TEST_CHAR_CLASS(islower, LOWER);
+}
+
+void test_ctype__isupper(void)
+{
TEST_CHAR_CLASS(isupper, UPPER);
+}
+
+void test_ctype__iscntrl(void)
+{
TEST_CHAR_CLASS(iscntrl, CNTRL);
+}
+
+void test_ctype__ispunct(void)
+{
TEST_CHAR_CLASS(ispunct, PUNCT);
+}
+
+void test_ctype__isxdigit(void)
+{
TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
+}
- return test_done();
+void test_ctype__isprint(void)
+{
+ TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
}
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h
index 66ec2387cc6..85e5d6a948a 100644
--- a/t/unit-tests/unit-test.h
+++ b/t/unit-tests/unit-test.h
@@ -1,3 +1,10 @@
#include "git-compat-util.h"
#include "clar/clar.h"
#include "clar-decls.h"
+#include "strbuf.h"
+
+#define cl_failf(fmt, ...) do { \
+ char desc[4096]; \
+ snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \
+ clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \
+} while (0)
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* [PATCH v8 14/14] clar: add CMake support
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (12 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 13/14] t/unit-tests: convert ctype " Patrick Steinhardt
@ 2024-09-04 14:17 ` Patrick Steinhardt
2024-09-04 14:32 ` [PATCH v8 00/14] Introduce clar testing framework phillip.wood123
14 siblings, 0 replies; 172+ messages in thread
From: Patrick Steinhardt @ 2024-09-04 14:17 UTC (permalink / raw)
To: git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Now that we're using `clar` as powerful test framework, we have to
adjust the Visual C build (read: the CMake definition) to be able to
handle that, too.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 832f46b316b..608fd3fe709 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -1004,6 +1004,59 @@ foreach(unit_test ${unit_test_PROGRAMS})
endif()
endforeach()
+parse_makefile_for_scripts(unit_tests_SUITES "UNIT_TESTS_SUITES" "")
+
+set(clar_decls "")
+set(clar_cbs "")
+set(clar_cbs_count 0)
+set(clar_suites "static struct clar_suite _clar_suites[] = {\n")
+list(LENGTH unit_tests_SUITES clar_suites_count)
+foreach(suite ${unit_tests_SUITES})
+ file(STRINGS "${CMAKE_SOURCE_DIR}/t/unit-tests/${suite}.c" decls
+ REGEX "^void test_${suite}__[a-zA-Z_0-9][a-zA-Z_0-9]*\\(void\\)$")
+
+ list(LENGTH decls decls_count)
+ string(REGEX REPLACE "void (test_${suite}__([a-zA-Z_0-9]*))\\(void\\)" " { \"\\2\", &\\1 },\n" cbs ${decls})
+ string(JOIN "" cbs ${cbs})
+ list(TRANSFORM decls PREPEND "extern ")
+ string(JOIN ";\n" decls ${decls})
+
+ string(APPEND clar_decls "${decls};\n")
+ string(APPEND clar_cbs
+ "static const struct clar_func _clar_cb_${suite}[] = {\n"
+ ${cbs}
+ "};\n")
+ string(APPEND clar_suites
+ " {\n"
+ " \"${suite}\",\n"
+ " { NULL, NULL },\n"
+ " { NULL, NULL },\n"
+ " _clar_cb_${suite}, ${decls_count}, 1\n"
+ " },\n")
+ math(EXPR clar_cbs_count "${clar_cbs_count}+${decls_count}")
+endforeach()
+string(APPEND clar_suites
+ "};\n"
+ "static const size_t _clar_suite_count = ${clar_suites_count};\n"
+ "static const size_t _clar_callback_count = ${clar_cbs_count};\n")
+file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h" "${clar_decls}")
+file(WRITE "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite" "${clar_decls}" "${clar_cbs}" "${clar_suites}")
+
+list(TRANSFORM unit_tests_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/")
+list(TRANSFORM unit_tests_SUITES APPEND ".c")
+add_library(unit-tests-lib ${unit_tests_SUITES} "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c")
+target_include_directories(unit-tests-lib PRIVATE "${CMAKE_SOURCE_DIR}/t/unit-tests")
+add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c")
+target_link_libraries(unit-tests unit-tests-lib common-main)
+set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+if(MSVC)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+endif()
+
#test-tool
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
--
2.46.0.519.g2e7b89e038.dirty
^ permalink raw reply related [flat|nested] 172+ messages in thread
* Re: [PATCH v8 00/14] Introduce clar testing framework
2024-09-04 14:16 ` [PATCH v8 " Patrick Steinhardt
` (13 preceding siblings ...)
2024-09-04 14:17 ` [PATCH v8 14/14] clar: add CMake support Patrick Steinhardt
@ 2024-09-04 14:32 ` phillip.wood123
14 siblings, 0 replies; 172+ messages in thread
From: phillip.wood123 @ 2024-09-04 14:32 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: René Scharfe, Junio C Hamano, Kyle Lippincott, Phillip Wood,
Josh Steadmon, rsbecker, Edward Thomson, Johannes Schindelin
Hi Patrick
On 04/09/2024 15:16, Patrick Steinhardt wrote:
>
> Changes compared to v7:
>
> - Properly wire up the "--immediate" flag.
>
> - Give a hint for the syntax to run only specific suites or tests for
> the "--run" and "--exclude" options.
As far as I'm concerned this version looks ready to be merged to next
Thanks
Phillip
> Thanks!
>
> Patrick
>
> Johannes Schindelin (4):
> clar: avoid compile error with mingw-w64
> clar(win32): avoid compile error due to unused `fs_copy()`
> clar: stop including `shellapi.h` unnecessarily
> clar: add CMake support
>
> Patrick Steinhardt (10):
> t: do not pass GIT_TEST_OPTS to unit tests with prove
> t: import the clar unit testing framework
> t/clar: fix compatibility with NonStop
> Makefile: fix sparse dependency on GENERATED_H
> Makefile: make hdr-check depend on generated headers
> Makefile: do not use sparse on third-party sources
> Makefile: wire up the clar unit testing framework
> t/unit-tests: implement test driver
> t/unit-tests: convert strvec tests to use clar
> t/unit-tests: convert ctype tests to use clar
>
> .gitignore | 1 +
> Documentation/technical/unit-tests.txt | 2 +
> Makefile | 53 +-
> contrib/buildsystems/CMakeLists.txt | 53 ++
> t/Makefile | 4 +-
> t/run-test.sh | 2 +-
> t/unit-tests/.gitignore | 2 +
> t/unit-tests/clar-generate.awk | 50 ++
> t/unit-tests/clar/.github/workflows/ci.yml | 23 +
> t/unit-tests/clar/COPYING | 15 +
> t/unit-tests/clar/README.md | 329 ++++++++
> t/unit-tests/clar/clar.c | 842 +++++++++++++++++++++
> t/unit-tests/clar/clar.h | 173 +++++
> t/unit-tests/clar/clar/fixtures.h | 50 ++
> t/unit-tests/clar/clar/fs.h | 524 +++++++++++++
> t/unit-tests/clar/clar/print.h | 211 ++++++
> t/unit-tests/clar/clar/sandbox.h | 159 ++++
> t/unit-tests/clar/clar/summary.h | 143 ++++
> t/unit-tests/clar/generate.py | 266 +++++++
> t/unit-tests/clar/test/.gitignore | 4 +
> t/unit-tests/clar/test/Makefile | 39 +
> t/unit-tests/clar/test/clar_test.h | 16 +
> t/unit-tests/clar/test/main.c | 40 +
> t/unit-tests/clar/test/main.c.sample | 27 +
> t/unit-tests/clar/test/resources/test/file | 1 +
> t/unit-tests/clar/test/sample.c | 84 ++
> t/unit-tests/{t-ctype.c => ctype.c} | 71 +-
> t/unit-tests/strvec.c | 241 ++++++
> t/unit-tests/t-strvec.c | 211 ------
> t/unit-tests/unit-test.c | 47 ++
> t/unit-tests/unit-test.h | 10 +
> 31 files changed, 3459 insertions(+), 234 deletions(-)
> create mode 100644 t/unit-tests/clar-generate.awk
> create mode 100644 t/unit-tests/clar/.github/workflows/ci.yml
> create mode 100644 t/unit-tests/clar/COPYING
> create mode 100644 t/unit-tests/clar/README.md
> create mode 100644 t/unit-tests/clar/clar.c
> create mode 100644 t/unit-tests/clar/clar.h
> create mode 100644 t/unit-tests/clar/clar/fixtures.h
> create mode 100644 t/unit-tests/clar/clar/fs.h
> create mode 100644 t/unit-tests/clar/clar/print.h
> create mode 100644 t/unit-tests/clar/clar/sandbox.h
> create mode 100644 t/unit-tests/clar/clar/summary.h
> create mode 100755 t/unit-tests/clar/generate.py
> create mode 100644 t/unit-tests/clar/test/.gitignore
> create mode 100644 t/unit-tests/clar/test/Makefile
> create mode 100644 t/unit-tests/clar/test/clar_test.h
> create mode 100644 t/unit-tests/clar/test/main.c
> create mode 100644 t/unit-tests/clar/test/main.c.sample
> create mode 100644 t/unit-tests/clar/test/resources/test/file
> create mode 100644 t/unit-tests/clar/test/sample.c
> rename t/unit-tests/{t-ctype.c => ctype.c} (68%)
> create mode 100644 t/unit-tests/strvec.c
> delete mode 100644 t/unit-tests/t-strvec.c
> create mode 100644 t/unit-tests/unit-test.c
> create mode 100644 t/unit-tests/unit-test.h
>
> Range-diff against v7:
> 1: b67f10ec0b0 = 1: b67f10ec0b0 t: do not pass GIT_TEST_OPTS to unit tests with prove
> 2: 55a9b46e65f = 2: 55a9b46e65f t: import the clar unit testing framework
> 3: f24401f0a87 = 3: f24401f0a87 t/clar: fix compatibility with NonStop
> 4: 658a601c541 = 4: 658a601c541 clar: avoid compile error with mingw-w64
> 5: 0b8a6ac5fed = 5: 0b8a6ac5fed clar(win32): avoid compile error due to unused `fs_copy()`
> 6: c50e7a0ea68 = 6: c50e7a0ea68 clar: stop including `shellapi.h` unnecessarily
> 7: b8f3f16dd27 = 7: b8f3f16dd27 Makefile: fix sparse dependency on GENERATED_H
> 8: 3d3fe443b9a = 8: 3d3fe443b9a Makefile: make hdr-check depend on generated headers
> 9: 7d0f494850a = 9: 7d0f494850a Makefile: do not use sparse on third-party sources
> 10: 9c74c5ae019 = 10: 9c74c5ae019 Makefile: wire up the clar unit testing framework
> 11: 8bd5b3e2b29 ! 11: 81d932bfa33 t/unit-tests: implement test driver
> @@ t/unit-tests/unit-test.c
> + struct string_list exclude_args = STRING_LIST_INIT_NODUP;
> + int immediate = 0;
> + struct option options[] = {
> -+ OPT_BOOL('i', "--immediate", &immediate,
> ++ OPT_BOOL('i', "immediate", &immediate,
> + N_("immediately exit upon the first failed test")),
> -+ OPT_STRING_LIST('r', "run", &run_args, N_("name"),
> -+ N_("run only test suite or individual test <name>")),
> -+ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("name"),
> -+ N_("exclude test suite <name>")),
> ++ OPT_STRING_LIST('r', "run", &run_args, N_("suite[::test]"),
> ++ N_("run only test suite or individual test <suite[::test]>")),
> ++ OPT_STRING_LIST('x', "exclude", &exclude_args, N_("suite"),
> ++ N_("exclude test suite <suite>")),
> + OPT_END(),
> + };
> + struct strvec args = STRVEC_INIT;
> @@ t/unit-tests/unit-test.c
> +
> + strvec_push(&args, argv[0]);
> + strvec_push(&args, "-t");
> ++ if (immediate)
> ++ strvec_push(&args, "-Q");
> + for (size_t i = 0; i < run_args.nr; i++)
> + strvec_pushf(&args, "-s%s", run_args.items[i].string);
> + for (size_t i = 0; i < exclude_args.nr; i++)
> 12: 3c3b9eacdfb = 12: 604303e31aa t/unit-tests: convert strvec tests to use clar
> 13: c8360db2f86 = 13: ba05b9f1eef t/unit-tests: convert ctype tests to use clar
> 14: d51c146cd9d = 14: 8441d29daa8 clar: add CMake support
^ permalink raw reply [flat|nested] 172+ messages in thread