Skip to contentSkip to conversation

Introduce clar testing framework

RFC PATCH Superseded Patrick Steinhardt 8 participants 172 messages Jul 31, 2024 – Sep 10, 2024 Page 1 of 2

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover Introduce clar testing framework

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.

- 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.

- 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.

Patrick

[1]: <[email protected]> [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
Signature · 1 line
2.46.0.dirty
#1 lore raw

RFC PATCH 1/3 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 will be wired up in subsequent commits.

---
 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
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
new file mode 100644
index 0000000000..b1ac2de460
@@ -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
t/unit-tests/clar/COPYING new file +15 -0
new file mode 100644
index 0000000000..8983817f0c
@@ -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.
t/unit-tests/clar/README.md new file +329 -0

329 lines not on this page · raw message

t/unit-tests/clar/clar.c new file +842 -0

842 lines not on this page · raw message

t/unit-tests/clar/clar.h new file +173 -0

173 lines not on this page · raw message

t/unit-tests/clar/clar/fixtures.h new file +50 -0

50 lines not on this page · raw message

t/unit-tests/clar/clar/fs.h new file +522 -0

522 lines not on this page · raw message

t/unit-tests/clar/clar/print.h new file +211 -0

211 lines not on this page · raw message

t/unit-tests/clar/clar/sandbox.h new file +154 -0

154 lines not on this page · raw message

t/unit-tests/clar/clar/summary.h new file +143 -0

143 lines not on this page · raw message

t/unit-tests/clar/generate.py new file +267 -0

267 lines not on this page · raw message

t/unit-tests/clar/test/.gitignore new file +5 -0
new file mode 100644
index 0000000000..3e8ae0a3a5
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 0000000000..93c6b2ad32
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 0000000000..0fcaa639aa
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 0000000000..59e56ad255
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 0000000000..a4d91b72fa
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 0000000000..220f4aa98a
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0

84 lines not on this page · raw message

Signature · 1 line
2.46.0.dirty
#21 lore raw

RFC PATCH 2/3 Makefile: wire up the clar unit testing framework

In reply to 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".

---
 .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
.gitignore +1 -0
index 8caf3700c2..6687bd6db4 100644
@@ -9,6 +9,7 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +30 -4
index d6479092a0..658acb4d48 100644
@@ -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
t/Makefile +1 -0
index 4c30e7c06f..55f740ae29 100644
@@ -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))
 
t/unit-tests/.gitignore +3 -0
index 5e56e040ec..b8d46f7bb1 100644
@@ -1 +1,4 @@
 /bin
+/.clarcache
+/clar.suite
+/clar-decls.h
t/unit-tests/unit-test.c new file +16 -0
new file mode 100644
index 0000000000..d7eecc384c
@@ -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;
+}
t/unit-tests/unit-test.h new file +3 -0
new file mode 100644
index 0000000000..99d59df1b0
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar.h"
+#include "clar-decls.h"
Signature · 1 line
2.46.0.dirty
#27 lore raw

RFC PATCH 3/3 t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

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.

---
 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%)
Makefile +1 -1
index 658acb4d48..8ebcbdc95a 100644
@@ -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))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +45 -79

276 lines not on this page · raw message

Signature · 1 line
2.46.0.dirty
#28 lore raw
In reply to Patrick Steinhardt
Quoted 3 lines · Patrick Steinhardt

- 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.

Quoted 4 lines

- 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.

Quoted 11 lines

- 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.

#29 lore raw
Quoted 29 lines · Junio C Hamano
Quoted 3 lines · Patrick Steinhardt

- 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.

Quoted 4 lines

- 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.

Quoted 9 lines

- 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.

Quoted 4 lines
Quoted 2 lines

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?

#22 lore raw

Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework

In reply to Patrick Steinhardt

Am 31.07.24 um 11:04 schrieb Patrick Steinhardt:

Quoted 31 lines

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

Review

Quoted 1 line
(quoted diff) +1 -0
+	$(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
.gitignore +0 -1
index 6687bd6db4..8caf3700c2 100644
@@ -9,7 +9,6 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
-/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +7 -13
index 8ebcbdc95a..1ffde38de5 100644
@@ -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
t/unit-tests/.gitignore +0 -1
index b8d46f7bb1..d0632ec7f9 100644
@@ -1,4 +1,3 @@
 /bin
-/.clarcache
 /clar.suite
 /clar-decls.h
t/unit-tests/generate.awk new file +47 -0

47 lines not on this page · raw message

--
2.46.0

#30 lore raw
In reply to [email protected]
Quoted 1 line · <[email protected]> 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.

#24 lore raw

Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework

In reply to Patrick Steinhardt

A trivial fix-up to be squashed into this step.

 Makefile | 1 +
 1 file changed, 1 insertion(+)
Makefile +1 -0
index 8ebcbdc95a..d561789582 100644
@@ -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
#2 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to Patrick Steinhardt
Quoted 3 lines · Patrick Steinhardt

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?

#35 lore raw
In reply to Patrick Steinhardt
Quoted 31 lines · Patrick Steinhardt

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.

Quoted 8 lines

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).

Quoted 3 lines

- 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.

Quoted 9 lines

- 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.

Quoted 7 lines

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

Quoted 61 lines

Patrick

[1]: <[email protected]> [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
Signature · 1 line
2.46.0.dirty
#3 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to Josh Steadmon
Quoted 7 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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.

#5 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to Josh Steadmon
Quoted 6 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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

Quoted 1 line

instead?

What are the requirements to build/use this?

#31 lore raw
In reply to Junio C Hamano
Quoted 8 lines · Junio C Hamano
Quoted 1 line · <[email protected]> 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.

Quoted 7 lines

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.

#32 lore raw
Quoted 14 lines · On Wednesday, July 31, 2024 4:25 PM, I wrote:
Quoted 7 lines · Junio C Hamano
Quoted 1 line · <[email protected]> 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.

Quoted 5 lines

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.

Quoted 7 lines
Quoted 3 lines

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

#25 lore raw

Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework

In reply to Junio C Hamano
Quoted 1 line · Junio C Hamano

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.

#36 lore raw
In reply to Josh Steadmon
Quoted 8 lines · Josh Steadmon
Quoted 2 lines · Patrick Steinhardt

- 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.

Quoted 6 lines

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

#34 lore raw
In reply to Junio C Hamano
Quoted 11 lines · Junio C Hamano
Quoted 3 lines · Patrick Steinhardt

- 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.

Quoted 8 lines
Quoted 4 lines

- 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

#33 lore raw
In reply to [email protected]
Quoted 23 lines · On Wed, Jul 31, 2024 at 04:37:37PM -0400, [email protected] wrote:
Quoted 14 lines · On Wednesday, July 31, 2024 4:25 PM, I wrote:
Quoted 7 lines · Junio C Hamano
Quoted 1 line · <[email protected]> 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.

Quoted 5 lines

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.

Quoted 7 lines
Quoted 3 lines

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.

Quoted 5 lines

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

#6 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to [email protected]
Quoted 11 lines · On Wed, Jul 31, 2024 at 04:04:29PM -0400, [email protected] wrote:
Quoted 6 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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

Quoted 1 line

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

#4 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to Junio C Hamano
Quoted 7 lines · Junio C Hamano
Quoted 5 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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.

Quoted 5 lines
Quoted 2 lines

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.

Quoted 3 lines

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

#23 lore raw

Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework

In reply to René Scharfe
Quoted 39 lines · René Scharfe

Am 31.07.24 um 11:04 schrieb Patrick Steinhardt:

Quoted 31 lines

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

Review

Quoted 1 line
(quoted diff) +1 -0
+	$(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.

Quoted 3 lines

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.

Quoted 3 lines

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

#26 lore raw

Re: [RFC PATCH 2/3] Makefile: wire up the clar unit testing framework

In reply to Junio C Hamano
Quoted 7 lines · Junio C Hamano
Quoted 1 line · Junio C Hamano

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

#7 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to Patrick Steinhardt
Quoted 7 lines · Patrick Steinhardt
Quoted 6 lines · On Wed, Jul 31, 2024 at 04:04:29PM -0400, [email protected] wrote:
Quoted 5 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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.

Quoted 7 lines
Quoted 5 lines
Quoted 1 line

Rather than forking our own copy of clar, could we just add it as a

submodule

Quoted 1 line

instead?

What are the requirements to build/use this?

In its current form, Python is a dependency due to "generate.py". I

mentioned

Quoted 3 lines

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).

#8 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 14 lines · On Thursday, August 1, 2024 8:15 AM, I wrote:
Quoted 13 lines · Patrick Steinhardt
Quoted 11 lines · On Wed, Jul 31, 2024 at 04:04:29PM -0400, [email protected] wrote:
Quoted 6 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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

Quoted 1 line

instead?

What are the requirements to build/use this?

Quoted 7 lines
Quoted 1 line

In its current form, Python is a dependency due to "generate.py". I

mentioned

Quoted 3 lines

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

Quoted 1 line

(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/

#9 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 25 lines · On Thu, Aug 01, 2024 at 08:54:36AM -0400, [email protected] wrote:
Quoted 14 lines · On Thursday, August 1, 2024 8:15 AM, I wrote:
Quoted 13 lines · Patrick Steinhardt
Quoted 11 lines · On Wed, Jul 31, 2024 at 04:04:29PM -0400, [email protected] wrote:
Quoted 6 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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

Quoted 1 line

instead?

What are the requirements to build/use this?

Quoted 7 lines
Quoted 1 line

In its current form, Python is a dependency due to "generate.py". I

mentioned

Quoted 3 lines

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

Quoted 1 line

(not portable).

Quoted 9 lines

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

#10 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 12 lines · Patrick Steinhardt
Quoted 11 lines · On Thu, Aug 01, 2024 at 08:54:36AM -0400, [email protected] wrote:
Quoted 10 lines · On Thursday, August 1, 2024 8:15 AM, I wrote:
Quoted 9 lines · Patrick Steinhardt
Quoted 8 lines · On Wed, Jul 31, 2024 at 04:04:29PM -0400, [email protected] wrote:
Quoted 7 lines · Josh Steadmon
Quoted 3 lines · Patrick Steinhardt

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

Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines

submodule

Quoted 1 line

instead?

What are the requirements to build/use this?

In its current form, Python is a dependency due to "generate.py". I

mentioned

Quoted 1 line

elsewhere though that I'd be happy to port it to our language of

choice.

Quoted 17 lines
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

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

Quoted 1 line

(not portable).

Quoted 8 lines

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/

Quoted 1 line

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 1 line

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?

#11 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 6 lines · On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected] wrote:
Quoted 1 line · Patrick Steinhardt

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 1 line

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

#12 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 10 lines · Patrick Steinhardt
Quoted 7 lines · On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected] wrote:
Quoted 2 lines · Patrick Steinhardt

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 1 line

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

Quoted 1 line

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 1 line

did a 1:1 import of the upstream dependency. We could just as well remove

it

Quoted 1 line

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?

#13 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 12 lines · On Thu, Aug 01, 2024 at 09:53:52AM -0400, [email protected] wrote:
Quoted 10 lines · Patrick Steinhardt
Quoted 7 lines · On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected] wrote:
Quoted 2 lines · Patrick Steinhardt

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 1 line

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

Quoted 8 lines
Quoted 1 line

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 1 line

did a 1:1 import of the upstream dependency. We could just as well remove

it

Quoted 1 line

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

#14 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 8 lines · Patrick Steinhardt
Quoted 7 lines · On Thu, Aug 01, 2024 at 09:53:52AM -0400, [email protected] wrote:
Quoted 6 lines · Patrick Steinhardt
Quoted 5 lines · On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected] wrote:
Quoted 2 lines · Patrick Steinhardt

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 1 line

whether the version I have proposed here works when running `make

test`?

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

That is the commit I have been trying to use. make test in clar or

git?

Quoted 9 lines
Quoted 9 lines
Quoted 3 lines

In Git itself. make test builds and runs our unit tests, and that now

also includes

Quoted 2 lines

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 2 lines

did a 1:1 import of the upstream dependency. We could just as well remove

Quoted 7 lines
Quoted 5 lines

it

Quoted 1 line

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

Quoted 1 line

been merged to any of the branches. You'd have to apply them on top of

v2.46.0

Quoted 1 line

first :)

Do you happen to have a public fork?
Regards,
Randall

#15 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 28 lines · On Thu, Aug 01, 2024 at 10:04:37AM -0400, [email protected] wrote:
Quoted 8 lines · Patrick Steinhardt
Quoted 7 lines · On Thu, Aug 01, 2024 at 09:53:52AM -0400, [email protected] wrote:
Quoted 6 lines · Patrick Steinhardt
Quoted 5 lines · On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected] wrote:
Quoted 2 lines · Patrick Steinhardt

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 1 line

whether the version I have proposed here works when running `make

test`?

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

That is the commit I have been trying to use. make test in clar or

git?

Quoted 15 lines
Quoted 14 lines
Quoted 3 lines

In Git itself. make test builds and runs our unit tests, and that now

also includes

Quoted 2 lines

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 2 lines

did a 1:1 import of the upstream dependency. We could just as well remove

it

Quoted 1 line

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?

Quoted 7 lines
Quoted 1 line

No, as the patches in this thread are only up for discussion right now and

have not

Quoted 1 line

been merged to any of the branches. You'd have to apply them on top of

v2.46.0

Quoted 1 line

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

#16 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

Quoted 35 lines · Patrick Steinhardt
Quoted 4 lines · On Thu, Aug 01, 2024 at 10:04:37AM -0400, [email protected] wrote:
Quoted 3 lines · Patrick Steinhardt
Quoted 2 lines · On Thu, Aug 01, 2024 at 09:53:52AM -0400, [email protected] wrote:
Quoted 1 line · Patrick Steinhardt

On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected]

Quoted 29 lines · wrote:
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines · Patrick Steinhardt
Quoted 2 lines

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 2 lines

whether the version I have proposed here works when running `make

test`?

Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

That is the commit I have been trying to use. make test in clar or

git?

Quoted 9 lines
Quoted 9 lines
Quoted 3 lines

In Git itself. make test builds and runs our unit tests, and that now

also includes

Quoted 2 lines

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 2 lines

did a 1:1 import of the upstream dependency. We could just as well remove

Quoted 8 lines
Quoted 5 lines

it

Quoted 1 line

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

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

been merged to any of the branches. You'd have to apply them on top of

v2.46.0

Quoted 1 line

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?

#17 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework

In reply to Patrick Steinhardt
Quoted 35 lines · Patrick Steinhardt
Quoted 4 lines · On Thu, Aug 01, 2024 at 10:04:37AM -0400, [email protected] wrote:
Quoted 3 lines · Patrick Steinhardt
Quoted 2 lines · On Thu, Aug 01, 2024 at 09:53:52AM -0400, [email protected] wrote:
Quoted 1 line · Patrick Steinhardt

On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected]

Quoted 29 lines · wrote:
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines · Patrick Steinhardt
Quoted 2 lines

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 2 lines

whether the version I have proposed here works when running `make

test`?

Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

That is the commit I have been trying to use. make test in clar or

git?

Quoted 9 lines
Quoted 9 lines
Quoted 3 lines

In Git itself. make test builds and runs our unit tests, and that now

also includes

Quoted 2 lines

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 2 lines

did a 1:1 import of the upstream dependency. We could just as well remove

Quoted 8 lines
Quoted 5 lines

it

Quoted 1 line

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

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

been merged to any of the branches. You'd have to apply them on top of

v2.46.0

Quoted 1 line

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:

(quoted diff) +0 -0

b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..81a40f26b8 100644

t/unit-tests/clar/clar/sandbox.h +1 -1
@@ -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.

#18 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework (better one)

In reply to Patrick Steinhardt
Quoted 35 lines · Patrick Steinhardt
Quoted 4 lines · On Thu, Aug 01, 2024 at 10:04:37AM -0400, [email protected] wrote:
Quoted 3 lines · Patrick Steinhardt
Quoted 2 lines · On Thu, Aug 01, 2024 at 09:53:52AM -0400, [email protected] wrote:
Quoted 1 line · Patrick Steinhardt

On Thu, Aug 01, 2024 at 09:47:38AM -0400, [email protected]

Quoted 29 lines · wrote:
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines · Patrick Steinhardt
Quoted 2 lines

As mentioned in another mail, we do not use its Makefile at all. Did you

check

Quoted 2 lines

whether the version I have proposed here works when running `make

test`?

Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

That is the commit I have been trying to use. make test in clar or

git?

Quoted 9 lines
Quoted 9 lines
Quoted 3 lines

In Git itself. make test builds and runs our unit tests, and that now

also includes

Quoted 2 lines

unit tests based on clar with this patch series. The clar Makefile exists

only because I

Quoted 2 lines

did a 1:1 import of the upstream dependency. We could just as well remove

Quoted 8 lines
Quoted 5 lines

it

Quoted 1 line

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

Quoted 8 lines
Quoted 6 lines
Quoted 2 lines

been merged to any of the branches. You'd have to apply them on top of

v2.46.0

Quoted 1 line

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):

(quoted diff) +0 -0

b/t/unit-tests/clar/clar/sandbox.h
index 0ba1479620..1b14dbfd76 100644

t/unit-tests/clar/clar/sandbox.h +6 -0
@@ -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

#19 lore raw

Re: [RFC PATCH 1/3] t: import the clar unit testing framework (better one)

Am 01.08.24 um 19:43 schrieb [email protected]:

Quoted 2 lines

I'm running the full test now and may report more or success.

"make unit-tests" would suffice.

René

#20 lore raw

RE: [RFC PATCH 1/3] t: import the clar unit testing framework (better one)

Quoted 5 lines · René Scharfe

Am 01.08.24 um 19:43 schrieb [email protected]:

Quoted 2 lines

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

#37 lore raw

v2 cover Introduce clar testing framework

In reply to Patrick Steinhardt

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

+ ## 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 <[email protected]>
     
      ## 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

Signature · 1 line
2.46.0.dirty
#38 lore raw

RFC PATCH v2 1/7 t: do not pass GIT_TEST_OPTS to unit tests with prove

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(-)
t/Makefile +2 -1
index 4c30e7c06f..d2212de0b7 100644
@@ -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):
t/run-test.sh +1 -1
index 13c353b91b..63328ac630 100755
@@ -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 "$@"
Signature · 1 line
2.46.0.dirty
#39 lore raw

RFC PATCH v2 2/7 t: import the clar unit testing framework

In reply to Patrick Steinhardt

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.

---
 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
Documentation/technical/unit-tests.txt +2 -0
index 206037ffb1..fc201d4b3d 100644
@@ -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
Makefile +3 -1
index 3863e60b66..674b0ac4e1 100644
@@ -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
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
new file mode 100644
index 0000000000..b1ac2de460
@@ -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
t/unit-tests/clar/COPYING new file +15 -0
new file mode 100644
index 0000000000..8983817f0c
@@ -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.
t/unit-tests/clar/README.md new file +329 -0

329 lines not on this page · raw message

t/unit-tests/clar/clar.c new file +842 -0

842 lines not on this page · raw message

t/unit-tests/clar/clar.h new file +173 -0

173 lines not on this page · raw message

t/unit-tests/clar/clar/fixtures.h new file +50 -0

50 lines not on this page · raw message

t/unit-tests/clar/clar/fs.h new file +522 -0

522 lines not on this page · raw message

t/unit-tests/clar/clar/print.h new file +211 -0

211 lines not on this page · raw message

t/unit-tests/clar/clar/sandbox.h new file +154 -0

154 lines not on this page · raw message

t/unit-tests/clar/clar/summary.h new file +143 -0

143 lines not on this page · raw message

t/unit-tests/clar/generate.py new file +267 -0

267 lines not on this page · raw message

t/unit-tests/clar/test/.gitignore new file +5 -0
new file mode 100644
index 0000000000..3e8ae0a3a5
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 0000000000..93c6b2ad32
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 0000000000..0fcaa639aa
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 0000000000..59e56ad255
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 0000000000..a4d91b72fa
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 0000000000..220f4aa98a
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0

84 lines not on this page · raw message

Signature · 1 line
2.46.0.dirty
#42 lore raw

RFC PATCH v2 3/7 t/clar: fix whitespace errors

In reply to Patrick Steinhardt

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

---
 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(-)
t/unit-tests/clar/clar/sandbox.h +2 -3
index 0ba1479620..7c177f3525 100644
@@ -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;
 }
-
t/unit-tests/clar/generate.py +0 -1
index 931b4d60d9..80996ac3e7 100755
@@ -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()))
-
t/unit-tests/clar/test/.gitignore +0 -1
index 3e8ae0a3a5..a477d0c40c 100644
@@ -2,4 +2,3 @@ clar.suite
 .clarcache
 clar_test
 *.o
-
Signature · 1 line
2.46.0.dirty
#43 lore raw

RFC PATCH v2 4/7 t/clar: fix compatibility with NonStop

In reply to Patrick Steinhardt

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

---
 t/unit-tests/clar/clar/sandbox.h | 6 ++++++
 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
index 7c177f3525..e25057b7c4 100644
@@ -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)
Signature · 1 line
2.46.0.dirty
#44 lore raw

RFC PATCH v2 5/7 Makefile: wire up the clar unit testing framework

In reply to 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".

---
 .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
.gitignore +1 -0
index 8caf3700c2..6687bd6db4 100644
@@ -9,6 +9,7 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +31 -5
index 674b0ac4e1..f56b14ad6d 100644
@@ -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
t/Makefile +1 -0
index d2212de0b7..131ffd778f 100644
@@ -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))
 
t/unit-tests/.gitignore +2 -0
index 5e56e040ec..d0632ec7f9 100644
@@ -1 +1,3 @@
 /bin
+/clar.suite
+/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0

50 lines not on this page · raw message

t/unit-tests/unit-test.c new file +18 -0
new file mode 100644
index 0000000000..32a81299e9
@@ -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;
+}
t/unit-tests/unit-test.h new file +3 -0
new file mode 100644
index 0000000000..99d59df1b0
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar.h"
+#include "clar-decls.h"
Signature · 1 line
2.46.0.dirty
#45 lore raw

RFC PATCH v2 6/7 t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

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.

---
 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%)
Makefile +1 -1
index f56b14ad6d..cf76f9b353 100644
@@ -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))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +43 -76

261 lines not on this page · raw message

t/unit-tests/unit-test.c +1 -2
index 32a81299e9..82b7635e6a 100644
@@ -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);
 
Signature · 1 line
2.46.0.dirty
#48 lore raw

RFC PATCH v2 7/7 t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt

Convert the ctype tests to use the new clar unit testing framework.

---
 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%)
Makefile +1 -1
index cf76f9b353..aa2bd5cec1 100644
@@ -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
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +58 -13

97 lines not on this page · raw message

Signature · 1 line
2.46.0.dirty
#40 lore raw

Re: [RFC PATCH v2 2/7] t: import the clar unit testing framework

In reply to Patrick Steinhardt
Quoted 59 lines · Patrick Steinhardt

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.

---
 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
Documentation/technical/unit-tests.txt +1 -0
index 206037ffb1..fc201d4b3d 100644
@@ -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"]
 |=====

Documentation/technical/unit-tests.txt Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui · line 217

Quoted 5 lines
Documentation/technical/unit-tests.txt +1 -0
@@ -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

#46 lore raw

Re: [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt
Quoted 118 lines · Patrick Steinhardt

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.

---
 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%)
Makefile +1 -1
index f56b14ad6d..cf76f9b353 100644
@@ -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))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +7 -13
similarity index 54%
rename from t/unit-tests/t-strvec.c
rename to t/unit-tests/strvec.c
index fa1a041469..d782c5f73b 100644
@@ -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)
 

t/unit-tests/strvec.c line 21

Quoted 10 lines
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +4 -4
-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?

Quoted 261 lines
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +32 -59
 }
 
-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();
-}
t/unit-tests/unit-test.c +1 -2
index 32a81299e9..82b7635e6a 100644
@@ -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);
 
Signature · 1 line
2.46.0.dirty
#41 lore raw

Re: [RFC PATCH v2 2/7] t: import the clar unit testing framework

In reply to Josh Steadmon
Quoted 10 lines · Josh Steadmon

Documentation/technical/unit-tests.txt Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui · line 217

Quoted 6 lines · Patrick Steinhardt
Documentation/technical/unit-tests.txt +1 -0
@@ -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

#47 lore raw

Re: [RFC PATCH v2 6/7] t/unit-tests: convert strvec tests to use clar

In reply to Josh Steadmon
Quoted 14 lines · Josh Steadmon

t/unit-tests/strvec.c line 21

Quoted 10 lines · Patrick Steinhardt
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +4 -4
-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

#49 lore raw

v3 cover Introduce clar testing framework

In reply to Patrick Steinhardt

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

Signature · 1 line
2.46.0.dirty
#50 lore raw

RFC PATCH v3 1/7 t: do not pass GIT_TEST_OPTS to unit tests with prove

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(-)
t/Makefile +2 -1
index 4c30e7c06f..d2212de0b7 100644
@@ -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):
t/run-test.sh +1 -1
index 13c353b91b..63328ac630 100755
@@ -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 "$@"
Signature · 1 line
2.46.0.dirty
#51 lore raw

RFC PATCH v3 2/7 t: import the clar unit testing framework

In reply to Patrick Steinhardt

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.

---
 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
Documentation/technical/unit-tests.txt +2 -0
index 206037ffb1..5a432b7b29 100644
@@ -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
Makefile +3 -1
index 3863e60b66..674b0ac4e1 100644
@@ -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
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
new file mode 100644
index 0000000000..b1ac2de460
@@ -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
t/unit-tests/clar/COPYING new file +15 -0
new file mode 100644
index 0000000000..8983817f0c
@@ -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.
t/unit-tests/clar/README.md new file +329 -0

329 lines not on this page · raw message

t/unit-tests/clar/clar.c new file +842 -0

842 lines not on this page · raw message

t/unit-tests/clar/clar.h new file +173 -0

173 lines not on this page · raw message

t/unit-tests/clar/clar/fixtures.h new file +50 -0

50 lines not on this page · raw message

t/unit-tests/clar/clar/fs.h new file +522 -0

522 lines not on this page · raw message

t/unit-tests/clar/clar/print.h new file +211 -0

211 lines not on this page · raw message

t/unit-tests/clar/clar/sandbox.h new file +154 -0

154 lines not on this page · raw message

t/unit-tests/clar/clar/summary.h new file +143 -0

143 lines not on this page · raw message

t/unit-tests/clar/generate.py new file +267 -0

267 lines not on this page · raw message

t/unit-tests/clar/test/.gitignore new file +5 -0
new file mode 100644
index 0000000000..3e8ae0a3a5
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 0000000000..93c6b2ad32
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 0000000000..0fcaa639aa
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 0000000000..59e56ad255
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 0000000000..a4d91b72fa
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 0000000000..220f4aa98a
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0

84 lines not on this page · raw message

Signature · 1 line
2.46.0.dirty
#52 lore raw

RFC PATCH v3 3/7 t/clar: fix whitespace errors

In reply to Patrick Steinhardt

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

---
 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(-)
t/unit-tests/clar/clar/sandbox.h +2 -3
index 0ba1479620..7c177f3525 100644
@@ -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;
 }
-
t/unit-tests/clar/generate.py +0 -1
index 931b4d60d9..80996ac3e7 100755
@@ -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()))
-
t/unit-tests/clar/test/.gitignore +0 -1
index 3e8ae0a3a5..a477d0c40c 100644
@@ -2,4 +2,3 @@ clar.suite
 .clarcache
 clar_test
 *.o
-
Signature · 1 line
2.46.0.dirty
#59 lore raw

RFC PATCH v3 4/7 t/clar: fix compatibility with NonStop

In reply to Patrick Steinhardt

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

---
 t/unit-tests/clar/clar/sandbox.h | 6 ++++++
 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
index 7c177f3525..e25057b7c4 100644
@@ -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)
Signature · 1 line
2.46.0.dirty
#60 lore raw

RFC PATCH v3 5/7 Makefile: wire up the clar unit testing framework

In reply to 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".

---
 .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
.gitignore +1 -0
index 8caf3700c2..6687bd6db4 100644
@@ -9,6 +9,7 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +30 -4
index 674b0ac4e1..d343703215 100644
@@ -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
t/Makefile +1 -0
index d2212de0b7..131ffd778f 100644
@@ -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))
 
t/unit-tests/.gitignore +2 -0
index 5e56e040ec..d0632ec7f9 100644
@@ -1 +1,3 @@
 /bin
+/clar.suite
+/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0

50 lines not on this page · raw message

t/unit-tests/unit-test.c new file +18 -0
new file mode 100644
index 0000000000..32a81299e9
@@ -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;
+}
t/unit-tests/unit-test.h new file +3 -0
new file mode 100644
index 0000000000..66ec2387cc
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
Signature · 1 line
2.46.0.dirty
#61 lore raw

RFC PATCH v3 6/7 t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

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.

---
 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%)
Makefile +1 -1
index d343703215..ec90b8ac35 100644
@@ -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))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +43 -76

261 lines not on this page · raw message

t/unit-tests/unit-test.c +1 -2
index 32a81299e9..82b7635e6a 100644
@@ -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);
 
Signature · 1 line
2.46.0.dirty
#62 lore raw

RFC PATCH v3 7/7 t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt

Convert the ctype tests to use the new clar unit testing framework.

---
 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%)
Makefile +1 -1
index ec90b8ac35..fa7c540bc0 100644
@@ -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
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +58 -13

97 lines not on this page · raw message

Signature · 1 line
2.46.0.dirty
#63 lore raw
In reply to Patrick Steinhardt
Quoted 22 lines · Patrick Steinhardt

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

Quoted 8 lines

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?

#64 lore raw
Quoted 32 lines · Josh Steadmon
Quoted 22 lines · Patrick Steinhardt

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

Quoted 8 lines

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

Quoted 1 line

I'm generally in favor of this change, but I'm still unsure what our plan

is for

Quoted 1 line

importing this from upstream clar. Are we going to vendor our own copy here

and

Quoted 1 line

(hopefully) someone will pay attention to upstream fixes and apply them to

our

Quoted 1 line

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.

#65 lore raw
In reply to Josh Steadmon
Quoted 5 lines · Josh Steadmon

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.

#66 lore raw
Quoted 15 lines · Junio C Hamano
Quoted 5 lines · Josh Steadmon

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.

Quoted 5 lines

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.

#67 lore raw
Quoted 8 lines · <[email protected]> writes:
Quoted 5 lines

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."

#68 lore raw
Quoted 17 lines · Junio C Hamano
Quoted 8 lines · <[email protected]> writes:
Quoted 5 lines

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.

Quoted 7 lines

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

#53 lore raw

Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors

In reply to Patrick Steinhardt
Quoted 11 lines · Patrick Steinhardt

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

---
 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.

#54 lore raw

RE: [RFC PATCH v3 3/7] t/clar: fix whitespace errors

Quoted 14 lines · Junio C Hamano
Quoted 11 lines · Patrick Steinhardt

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

---
 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(-)
Quoted 6 lines

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?

#55 lore raw

Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors

Quoted 15 lines · <[email protected]> writes:
Quoted 14 lines · Junio C Hamano
Quoted 11 lines · Patrick Steinhardt

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

---
 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(-)
Quoted 8 lines
Quoted 6 lines

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'.

#56 lore raw

RE: [RFC PATCH v3 3/7] t/clar: fix whitespace errors

Quoted 23 lines · Junio C Hamano
Quoted 15 lines · <[email protected]> writes:
Quoted 14 lines · Junio C Hamano
Quoted 11 lines · Patrick Steinhardt

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

---
 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(-)
Quoted 4 lines
Quoted 4 lines

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.

Quoted 1 line
Quoted 1 line
Quoted 8 lines
Quoted 3 lines
Quoted 1 line

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.

#57 lore raw

Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors

In reply to Junio C Hamano
Quoted 14 lines · Junio C Hamano
Quoted 11 lines · Patrick Steinhardt

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

---
 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(-)
Quoted 7 lines

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(-)
t/unit-tests/clar/clar/fs.h +1 -1
index a6eda5e5dc..3e39890bd3 100644
@@ -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);
 
#58 lore raw

Re: [RFC PATCH v3 3/7] t/clar: fix whitespace errors

Quoted 37 lines · Junio C Hamano
Quoted 14 lines · Junio C Hamano
Quoted 11 lines · Patrick Steinhardt

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

---
 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(-)
Quoted 7 lines

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(-)
t/unit-tests/clar/clar/fs.h +0 -0
index a6eda5e5dc..3e39890bd3 100644

t/unit-tests/clar/clar/fs.h fs_rm_wait(WCHAR *_wpath) · line 151

Quoted 7 lines
t/unit-tests/clar/clar/fs.h +1 -1
@@ -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

#69 lore raw

v4 cover Introduce clar testing framework

In reply to Patrick Steinhardt

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

+ ## 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
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#71 lore raw

PATCH v4 2/7 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 will be wired up in subsequent commits.

---
 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
Documentation/technical/unit-tests.txt +2 -0
index 206037ffb1..5a432b7b29 100644
@@ -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
Makefile +3 -1
index 3863e60b66..674b0ac4e1 100644
@@ -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
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
new file mode 100644
index 0000000000..b1ac2de460
@@ -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
t/unit-tests/clar/COPYING new file +15 -0
new file mode 100644
index 0000000000..8983817f0c
@@ -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.
t/unit-tests/clar/README.md new file +329 -0

329 lines not on this page · raw message

t/unit-tests/clar/clar.c new file +842 -0

842 lines not on this page · raw message

t/unit-tests/clar/clar.h new file +173 -0

173 lines not on this page · raw message

t/unit-tests/clar/clar/fixtures.h new file +50 -0

50 lines not on this page · raw message

t/unit-tests/clar/clar/fs.h new file +522 -0

522 lines not on this page · raw message

t/unit-tests/clar/clar/print.h new file +211 -0

211 lines not on this page · raw message

t/unit-tests/clar/clar/sandbox.h new file +154 -0

154 lines not on this page · raw message

t/unit-tests/clar/clar/summary.h new file +143 -0

143 lines not on this page · raw message

t/unit-tests/clar/generate.py new file +267 -0

267 lines not on this page · raw message

t/unit-tests/clar/test/.gitignore new file +5 -0
new file mode 100644
index 0000000000..3e8ae0a3a5
@@ -0,0 +1,5 @@
+clar.suite
+.clarcache
+clar_test
+*.o
+
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 0000000000..93c6b2ad32
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 0000000000..0fcaa639aa
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 0000000000..59e56ad255
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 0000000000..a4d91b72fa
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 0000000000..220f4aa98a
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0

84 lines not on this page · raw message

Signature · 1 line
2.46.0.46.g406f326d27.dirty
#73 lore raw

PATCH v4 4/7 t/clar: fix compatibility with NonStop

In reply to Patrick Steinhardt

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

---
 t/unit-tests/clar/clar/sandbox.h | 6 ++++++
 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
index 7c177f3525..e25057b7c4 100644
@@ -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)
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#75 lore raw

PATCH v4 6/7 t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

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.

---
 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%)
Makefile +1 -1
index 1311bbbbed..ebc12b2bf1 100644
@@ -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))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +43 -76

261 lines not on this page · raw message

t/unit-tests/unit-test.c +1 -2
index 32a81299e9..82b7635e6a 100644
@@ -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);
 
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#76 lore raw

PATCH v4 7/7 t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt

Convert the ctype tests to use the new clar unit testing framework.

---
 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%)
Makefile +1 -1
index ebc12b2bf1..d28faabbf4 100644
@@ -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
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +58 -13

97 lines not on this page · raw message

Signature · 1 line
2.46.0.46.g406f326d27.dirty
#70 lore raw

PATCH v4 1/7 t: do not pass GIT_TEST_OPTS to unit tests with prove

In reply to Patrick Steinhardt

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(-)
t/Makefile +2 -1
index 4c30e7c06f..d2212de0b7 100644
@@ -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):
t/run-test.sh +1 -1
index 13c353b91b..63328ac630 100755
@@ -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 "$@"
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#72 lore raw

PATCH v4 3/7 t/clar: fix whitespace errors

In reply to Patrick Steinhardt

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

---
 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(-)
t/unit-tests/clar/clar/fs.h +1 -1
index a6eda5e5dc..3e39890bd3 100644
@@ -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);
 
t/unit-tests/clar/clar/sandbox.h +2 -3
index 0ba1479620..7c177f3525 100644
@@ -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;
 }
-
t/unit-tests/clar/generate.py +0 -1
index 931b4d60d9..80996ac3e7 100755
@@ -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()))
-
t/unit-tests/clar/test/.gitignore +0 -1
index 3e8ae0a3a5..a477d0c40c 100644
@@ -2,4 +2,3 @@ clar.suite
 .clarcache
 clar_test
 *.o
-
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#74 lore raw

PATCH v4 5/7 Makefile: wire up the clar unit testing framework

In reply to 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".

---
 .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
.gitignore +1 -0
index 8caf3700c2..6687bd6db4 100644
@@ -9,6 +9,7 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +31 -4
index 674b0ac4e1..1311bbbbed 100644
@@ -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
t/Makefile +1 -0
index d2212de0b7..131ffd778f 100644
@@ -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))
 
t/unit-tests/.gitignore +2 -0
index 5e56e040ec..d0632ec7f9 100644
@@ -1 +1,3 @@
 /bin
+/clar.suite
+/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0

50 lines not on this page · raw message

t/unit-tests/unit-test.c new file +18 -0
new file mode 100644
index 0000000000..32a81299e9
@@ -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;
+}
t/unit-tests/unit-test.h new file +3 -0
new file mode 100644
index 0000000000..66ec2387cc
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#77 lore raw
In reply to Patrick Steinhardt
Quoted 7 lines · Patrick Steinhardt

Hi,

this is the fourth version of my patch series that introduces the clar testing framework for our unit tests.

Changes compared to v3:

Quoted 8 lines

- 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.

Quoted 8 lines

+@@ 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.

#78 lore raw
Quoted 35 lines · Junio C Hamano
Quoted 7 lines · Patrick Steinhardt

Hi,

this is the fourth version of my patch series that introduces the clar testing framework for our unit tests.

Changes compared to v3:

Quoted 8 lines

- 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....
Quoted 1 line

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

#79 lore raw

v5 cover Introduce clar testing framework

In reply to Patrick Steinhardt

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 <[email protected]>
+
## 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 <[email protected]>
    @@ 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 <[email protected]>

## 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.

- ## 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

Signature · 1 line
2.46.0.46.g406f326d27.dirty
#80 lore raw

PATCH v5 1/9 t: do not pass GIT_TEST_OPTS to unit tests with prove

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(-)
t/Makefile +2 -1
index 4c30e7c06f..d2212de0b7 100644
@@ -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):
t/run-test.sh +1 -1
index 13c353b91b..63328ac630 100755
@@ -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 "$@"
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#81 lore raw

PATCH v5 2/9 t: import the clar unit testing framework

In reply to Patrick Steinhardt

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.

---
 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
Documentation/technical/unit-tests.txt +2 -0
index 206037ffb1..5a432b7b29 100644
@@ -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
Makefile +3 -1
index 3863e60b66..674b0ac4e1 100644
@@ -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
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
new file mode 100644
index 0000000000..b1ac2de460
@@ -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
t/unit-tests/clar/COPYING new file +15 -0
new file mode 100644
index 0000000000..8983817f0c
@@ -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.
t/unit-tests/clar/README.md new file +329 -0

329 lines not on this page · raw message

t/unit-tests/clar/clar.c new file +842 -0

842 lines not on this page · raw message

t/unit-tests/clar/clar.h new file +173 -0

173 lines not on this page · raw message

t/unit-tests/clar/clar/fixtures.h new file +50 -0

50 lines not on this page · raw message

t/unit-tests/clar/clar/fs.h new file +522 -0

522 lines not on this page · raw message

t/unit-tests/clar/clar/print.h new file +211 -0

211 lines not on this page · raw message

t/unit-tests/clar/clar/sandbox.h new file +153 -0

153 lines not on this page · raw message

t/unit-tests/clar/clar/summary.h new file +143 -0

143 lines not on this page · raw message

t/unit-tests/clar/generate.py new file +266 -0

266 lines not on this page · raw message

t/unit-tests/clar/test/.gitignore new file +4 -0
new file mode 100644
index 0000000000..a477d0c40c
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 0000000000..93c6b2ad32
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 0000000000..0fcaa639aa
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 0000000000..59e56ad255
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 0000000000..a4d91b72fa
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 0000000000..220f4aa98a
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0

84 lines not on this page · raw message

Signature · 1 line
2.46.0.46.g406f326d27.dirty
#91 lore raw

PATCH v5 4/9 Makefile: fix sparse dependency on GENERATED_H

In reply to Patrick Steinhardt

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.

---
 Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Makefile +2 -2
index 674b0ac4e1..0736d3c88e 100644
@@ -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"; \
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#92 lore raw

PATCH v5 5/9 Makefile: make hdr-check depend on generated headers

In reply to Patrick Steinhardt

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.

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Makefile +1 -1
index 0736d3c88e..5384bf8104 100644
@@ -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)
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#95 lore raw

PATCH v5 8/9 t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

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.

---
 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%)
Makefile +1 -1
index e6206ca8c7..b0c9a79c55 100644
@@ -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))
t/unit-tests/strvec.c renamed from t/unit-tests/t-strvec.c +43 -76

261 lines not on this page · raw message

t/unit-tests/unit-test.c +1 -2
index 32a81299e9..82b7635e6a 100644
@@ -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);
 
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#99 lore raw

PATCH v5 9/9 t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt

Convert the ctype tests to use the new clar unit testing framework.

---
 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%)
Makefile +1 -1
index b0c9a79c55..000eca9f86 100644
@@ -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
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +58 -13

97 lines not on this page · raw message

Signature · 1 line
2.46.0.46.g406f326d27.dirty
#93 lore raw

PATCH v5 6/9 Makefile: do not use sparse on third-party sources

In reply to Patrick Steinhardt

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.

---
 Makefile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Makefile +2 -1
index 5384bf8104..377ea7e144 100644
@@ -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) \
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#94 lore raw

PATCH v5 7/9 Makefile: wire up the clar unit testing framework

In reply to 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".

---
 .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
.gitignore +1 -0
index 8caf3700c2..6687bd6db4 100644
@@ -9,6 +9,7 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +32 -4
index 377ea7e144..e6206ca8c7 100644
@@ -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
t/Makefile +1 -0
index d2212de0b7..131ffd778f 100644
@@ -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))
 
t/unit-tests/.gitignore +2 -0
index 5e56e040ec..d0632ec7f9 100644
@@ -1 +1,3 @@
 /bin
+/clar.suite
+/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0

50 lines not on this page · raw message

t/unit-tests/unit-test.c new file +18 -0
new file mode 100644
index 0000000000..32a81299e9
@@ -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;
+}
t/unit-tests/unit-test.h new file +3 -0
new file mode 100644
index 0000000000..66ec2387cc
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#90 lore raw

PATCH v5 3/9 t/clar: fix compatibility with NonStop

In reply to Patrick Steinhardt

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

---
 t/unit-tests/clar/clar/sandbox.h | 6 ++++++
 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
index 7c177f3525..e25057b7c4 100644
@@ -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)
Signature · 1 line
2.46.0.46.g406f326d27.dirty
#103 lore raw
In reply to Patrick Steinhardt

Hi Patrick

Quoted 4 lines · Patrick Steinhardt

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

Quoted 301 lines

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 <[email protected]>
+
## 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 <[email protected]>
     @@ 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 <[email protected]>

## 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.

- ## 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

#82 lore raw

Re: [PATCH v5 2/9] t: import the clar unit testing framework

In reply to Patrick Steinhardt

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/[email protected]/

t/unit-tests/clar/clar.c line 69

Quoted 10 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c +7 -0
[...]
+#ifdef _WIN32
[...]
+#	ifndef PRIuZ
+#		define PRIuZ "Iu"
+#	endif
+#	ifndef PRIxZ
+#		define PRIxZ "Ix"
+#	endif

Thanks

Phillip

Quoted 2,585 lines
t/unit-tests/clar/clar.c +772 -0
+#	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"
t/unit-tests/clar/clar.h new file +173 -0
new file mode 100644
index 0000000000..8c22382bd5
@@ -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
t/unit-tests/clar/clar/fixtures.h new file +50 -0
new file mode 100644
index 0000000000..6ec6423484
@@ -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
t/unit-tests/clar/clar/fs.h new file +522 -0
new file mode 100644
index 0000000000..3e39890bd3
@@ -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
t/unit-tests/clar/clar/print.h new file +211 -0
new file mode 100644
index 0000000000..c17e2f693b
@@ -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);
+}
t/unit-tests/clar/clar/sandbox.h new file +153 -0
new file mode 100644
index 0000000000..7c177f3525
@@ -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;
+}
t/unit-tests/clar/clar/summary.h new file +143 -0
new file mode 100644
index 0000000000..4dd352e28b
@@ -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(&timestamp);
+	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;
+}
t/unit-tests/clar/generate.py new file +266 -0
new file mode 100755
index 0000000000..80996ac3e7
@@ -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()))
t/unit-tests/clar/test/.gitignore new file +4 -0
new file mode 100644
index 0000000000..a477d0c40c
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 0000000000..93c6b2ad32
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 0000000000..0fcaa639aa
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 0000000000..59e56ad255
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 0000000000..a4d91b72fa
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 0000000000..220f4aa98a
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0
new file mode 100644
index 0000000000..faa1209262
@@ -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);
+}
#96 lore raw

Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

Hi Patrick

t/unit-tests/unit-test.c int cmd_main(int argc, const char **argv) · line 12

Quoted 13 lines · Patrick Steinhardt
t/unit-tests/unit-test.c +1 -2
index 32a81299e9..82b7635e6a 100644
@@ -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

#100 lore raw

Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt

Hi Patrick

Quoted 5 lines · Patrick Steinhardt
  #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)); \
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +0 -1
-	int skip = test__run_begin(); \

t/unit-tests/ctype.c line 8

Quoted 10 lines
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +2 -8
-	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

Quoted 80 lines
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +55 -3
+	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 " ");
  }
#97 lore raw

Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar

In reply to Phillip Wood
Quoted 12 lines · Phillip Wood

Hi Patrick

Quoted 9 lines · Patrick Steinhardt
t/unit-tests/unit-test.c +0 -0
index 32a81299e9..82b7635e6a 100644
@@ -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. */

t/unit-tests/unit-test.c +0 -1
-	ALLOC_ARRAY(argv_copy, argc + 2);
Quoted 8 lines

t/unit-tests/unit-test.c int cmd_main(int argc, const char **argv) · line 12

Quoted 4 lines
t/unit-tests/unit-test.c +1 -1
+	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.

#102 lore raw

Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt
Quoted 8 lines · Patrick Steinhardt

Convert the ctype tests to use the new clar unit testing framework.

---
 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.

Quoted 17 lines

#define TEST_CHAR_CLASS(class, string) do { \

t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +3 -9
 	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)
#85 lore raw

Re: [PATCH v5 2/9] t: import the clar unit testing framework

In reply to Patrick Steinhardt

t/unit-tests/clar/clar.c line 25

Quoted 13 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new file +6 -0
new file mode 100644
index 0000000000..3fc2c76815
@@ -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.

#86 lore raw

RE: [PATCH v5 2/9] t: import the clar unit testing framework

Quoted 22 lines · Junio C Hamano

t/unit-tests/clar/clar.c line 25

Quoted 12 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new renamed from t/unit-tests/clar/clar.c +0 -0

file mode 100644 index 0000000000..3fc2c76815

t/unit-tests/clar/clar.c new file +6 -0
@@ -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>
       |          ^~~~~~~~~~~~
Quoted 7 lines

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.

#87 lore raw

Re: [PATCH v5 2/9] t: import the clar unit testing framework

Quoted 25 lines · <[email protected]> writes:
Quoted 24 lines · Junio C Hamano

t/unit-tests/clar/clar.c line 25

Quoted 12 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new renamed from t/unit-tests/clar/clar.c +0 -0

file mode 100644 index 0000000000..3fc2c76815

t/unit-tests/clar/clar.c new file +6 -0
@@ -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.

Quoted 7 lines
Quoted 5 lines

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"?

#88 lore raw

RE: [PATCH v5 2/9] t: import the clar unit testing framework

Quoted 30 lines · Junio C Hamano
Quoted 18 lines · <[email protected]> writes:
Quoted 17 lines · Junio C Hamano

t/unit-tests/clar/clar.c line 25

Quoted 12 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new renamed from t/unit-tests/clar/clar.c +0 -0

file mode 100644 index 0000000000..3fc2c76815

t/unit-tests/clar/clar.c new file +6 -0
@@ -0,0 +1,842 @@
...
+
+#ifdef _WIN32
+#	include <windows.h>
+#	include <io.h>
+#	include <shellapi.h>
+#	include <direct.h>

This seems to break Windows build

Quoted 2 lines
Quoted 2 lines

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:

Quoted 7 lines
Quoted 7 lines

No such file or directory

    89 | #include <shellapi.h>
       |          ^~~~~~~~~~~~

compilation terminated.

https://github.com/git/git/actions/runs/10459342252/job/28963152995

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

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.

#98 lore raw

Re: [PATCH v5 8/9] t/unit-tests: convert strvec tests to use clar

In reply to Junio C Hamano
Quoted 23 lines · Junio C Hamano
Quoted 12 lines · Phillip Wood

Hi Patrick

Quoted 9 lines · Patrick Steinhardt
t/unit-tests/unit-test.c +0 -0
index 32a81299e9..82b7635e6a 100644
@@ -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. */

t/unit-tests/unit-test.c +0 -1
-	ALLOC_ARRAY(argv_copy, argc + 2);
Quoted 8 lines

t/unit-tests/unit-test.c int cmd_main(int argc, const char **argv) · line 12

Quoted 4 lines
t/unit-tests/unit-test.c +1 -1
+	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?

Quoted 7 lines

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.

Quoted 6 lines

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

#89 lore raw

Re: [PATCH v5 2/9] t: import the clar unit testing framework

In reply to Junio C Hamano
Quoted 22 lines · Junio C Hamano

t/unit-tests/clar/clar.c line 25

Quoted 13 lines · Patrick Steinhardt
t/unit-tests/clar/clar.c new file +6 -0
new file mode 100644
index 0000000000..3fc2c76815
@@ -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>
        |          ^~~~~~~~~~~~
Quoted 7 lines

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

#101 lore raw

Re: [PATCH v5 9/9] t/unit-tests: convert ctype tests to use clar

In reply to Phillip Wood
Quoted 21 lines · Phillip Wood

Hi Patrick

Quoted 5 lines · Patrick Steinhardt
  #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)); \
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +0 -1
-	int skip = test__run_begin(); \

t/unit-tests/ctype.c line 8

Quoted 10 lines
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +2 -8
-	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?

Quoted 1 line

Thanks

I'll introduce a new function cl_failf() that allows us to print information like this.

Patrick

#104 lore raw
In reply to Phillip Wood
Quoted 23 lines · Phillip Wood

Hi Patrick

Quoted 4 lines · Patrick Steinhardt

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.

Quoted 7 lines

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?

Quoted 5 lines

- 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.

Quoted 6 lines

- 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.

Quoted 4 lines

- 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, ...))).

Quoted 4 lines

- 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.

Quoted 3 lines

- 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.

Quoted 2 lines

- 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.

Quoted 2 lines

- 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

#106 lore raw

v6 cover Introduce clar testing framework

In reply to Patrick Steinhardt

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 <[email protected]>
     
    @@ 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

Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#107 lore raw

PATCH v6 1/13 t: do not pass GIT_TEST_OPTS to unit tests with prove

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(-)
t/Makefile +2 -1
index 4c30e7c06fb..d2212de0b78 100644
@@ -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):
t/run-test.sh +1 -1
index 13c353b91b4..63328ac630c 100755
@@ -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 "$@"
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#108 lore raw

PATCH v6 2/13 t: import the clar unit testing framework

In reply to Patrick Steinhardt

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.

---
 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
Documentation/technical/unit-tests.txt +2 -0
index 206037ffb19..5a432b7b29c 100644
@@ -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
Makefile +3 -1
index a87e18b317d..28742a60964 100644
@@ -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
t/unit-tests/clar/.github/workflows/ci.yml new file +23 -0
new file mode 100644
index 00000000000..b1ac2de460a
@@ -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
t/unit-tests/clar/COPYING new file +15 -0
new file mode 100644
index 00000000000..8983817f0c9
@@ -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.
t/unit-tests/clar/README.md new file +329 -0

329 lines not on this page · raw message

t/unit-tests/clar/clar.c new file +842 -0

842 lines not on this page · raw message

t/unit-tests/clar/clar.h new file +173 -0

173 lines not on this page · raw message

t/unit-tests/clar/clar/fixtures.h new file +50 -0

50 lines not on this page · raw message

t/unit-tests/clar/clar/fs.h new file +522 -0

522 lines not on this page · raw message

t/unit-tests/clar/clar/print.h new file +211 -0

211 lines not on this page · raw message

t/unit-tests/clar/clar/sandbox.h new file +153 -0

153 lines not on this page · raw message

t/unit-tests/clar/clar/summary.h new file +143 -0

143 lines not on this page · raw message

t/unit-tests/clar/generate.py new file +266 -0

266 lines not on this page · raw message

t/unit-tests/clar/test/.gitignore new file +4 -0
new file mode 100644
index 00000000000..a477d0c40ca
@@ -0,0 +1,4 @@
+clar.suite
+.clarcache
+clar_test
+*.o
t/unit-tests/clar/test/Makefile new file +39 -0
new file mode 100644
index 00000000000..93c6b2ad32c
@@ -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
t/unit-tests/clar/test/clar_test.h new file +16 -0
new file mode 100644
index 00000000000..0fcaa639aa8
@@ -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
t/unit-tests/clar/test/main.c new file +40 -0
new file mode 100644
index 00000000000..59e56ad255b
@@ -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;
+}
t/unit-tests/clar/test/main.c.sample new file +27 -0
new file mode 100644
index 00000000000..a4d91b72fa8
@@ -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);
+}
t/unit-tests/clar/test/resources/test/file new file +1 -0
new file mode 100644
index 00000000000..220f4aa98a7
@@ -0,0 +1 @@
+File
t/unit-tests/clar/test/sample.c new file +84 -0

84 lines not on this page · raw message

Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#111 lore raw

PATCH v6 3/13 t/clar: fix compatibility with NonStop

In reply to Patrick Steinhardt

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

---
 t/unit-tests/clar/clar/sandbox.h | 6 ++++++
 1 file changed, 6 insertions(+)
t/unit-tests/clar/clar/sandbox.h +6 -0
index 7c177f35258..e25057b7c49 100644
@@ -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)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#112 lore raw

PATCH v6 4/13 clar: avoid compile error with mingw-w64

In reply to Patrick Steinhardt

From: Johannes Schindelin <[email protected]>

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).

---
 t/unit-tests/clar/clar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
index 3fc2c768158..e2ebe551d38 100644
@@ -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;
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#113 lore raw

PATCH v6 5/13 clar(win32): avoid compile error due to unused `fs_copy()`

In reply to Patrick Steinhardt

From: Johannes Schindelin <[email protected]>

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.

---
 t/unit-tests/clar/clar/fs.h | 2 ++
 1 file changed, 2 insertions(+)
t/unit-tests/clar/clar/fs.h +2 -0
index 3e39890bd3e..8b206179fc4 100644
@@ -297,6 +297,8 @@ cl_fs_cleanup(void)
 {
 #ifdef CLAR_FIXTURE_PATH
 	fs_rm(fixture_path(_clar_path, "*"));
+#else
+	((void)fs_copy); /* unused */
 #endif
 }
 
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#114 lore raw

PATCH v6 6/13 clar: stop including `shellapi.h` unnecessarily

In reply to Patrick Steinhardt

From: Johannes Schindelin <[email protected]>

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.

---
 t/unit-tests/clar/clar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
t/unit-tests/clar/clar.c +1 -1
index e2ebe551d38..cef0f023c24 100644
@@ -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
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#115 lore raw

PATCH v6 7/13 Makefile: fix sparse dependency on GENERATED_H

In reply to Patrick Steinhardt

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.

---
 Makefile | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
Makefile +2 -2
index 28742a60964..efd305ab358 100644
@@ -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"; \
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#116 lore raw

PATCH v6 8/13 Makefile: make hdr-check depend on generated headers

In reply to Patrick Steinhardt

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.

---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Makefile +1 -1
index efd305ab358..8c4487dd0c6 100644
@@ -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)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#117 lore raw

PATCH v6 9/13 Makefile: do not use sparse on third-party sources

In reply to Patrick Steinhardt

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.

---
 Makefile | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Makefile +2 -1
index 8c4487dd0c6..81a47b61327 100644
@@ -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) \
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#118 lore raw

PATCH v6 10/13 Makefile: wire up the clar unit testing framework

In reply to 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".

---
 .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
.gitignore +1 -0
index 8caf3700c23..6687bd6db4c 100644
@@ -9,6 +9,7 @@
 /GIT-PYTHON-VARS
 /GIT-SCRIPT-DEFINES
 /GIT-SPATCH-DEFINES
+/GIT-TEST-SUITES
 /GIT-USER-AGENT
 /GIT-VERSION-FILE
 /bin-wrappers/
Makefile +32 -4
index 81a47b61327..e38146b5eb0 100644
@@ -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
t/Makefile +1 -0
index d2212de0b78..131ffd778fe 100644
@@ -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))
 
t/unit-tests/.gitignore +2 -0
index 5e56e040ec0..d0632ec7f9e 100644
@@ -1 +1,3 @@
 /bin
+/clar.suite
+/clar-decls.h
t/unit-tests/clar-generate.awk new file +50 -0

50 lines not on this page · raw message

t/unit-tests/unit-test.c new file +18 -0
new file mode 100644
index 00000000000..32a81299e91
@@ -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;
+}
t/unit-tests/unit-test.h new file +3 -0
new file mode 100644
index 00000000000..66ec2387cc6
@@ -0,0 +1,3 @@
+#include "git-compat-util.h"
+#include "clar/clar.h"
+#include "clar-decls.h"
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#119 lore raw

PATCH v6 11/13 t/unit-tests: convert strvec tests to use clar

In reply to Patrick Steinhardt

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.

---
 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
Makefile +1 -1
index e38146b5eb0..56ce6c00e44 100644
@@ -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))
t/unit-tests/strvec.c new file +222 -0

222 lines not on this page · raw message

t/unit-tests/t-strvec.c deleted +0 -211

211 lines not on this page · raw message

Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#125 lore raw

PATCH v6 12/13 t/unit-tests: convert ctype tests to use clar

In reply to Patrick Steinhardt

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
---
 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%)
Makefile +1 -1
index 56ce6c00e44..c841cf70063 100644
@@ -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
t/unit-tests/ctype.c renamed from t/unit-tests/t-ctype.c +59 -11

96 lines not on this page · raw message

t/unit-tests/unit-test.h +7 -0
index 66ec2387cc6..4c461defe16 100644
@@ -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)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty
#128 lore raw

PATCH v6 13/13 clar: add CMake support

In reply to Patrick Steinhardt

From: Johannes Schindelin <[email protected]>

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.

---
 contrib/buildsystems/CMakeLists.txt | 53 +++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
contrib/buildsystems/CMakeLists.txt +53 -0
index 832f46b316b..608fd3fe709 100644
@@ -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)
Signature · 1 line
2.46.0.164.g477ce5ccd6.dirty