git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2 4/4] t-ctype: avoid duplicating class names

From
René Scharfe <l.s.r@web.de>
Date
Mar 10, 2024, 12:48 UTC
Message-ID
<69e24019-1fff-40df-9aef-cdfd058124c9@web.de>
In-Reply-To
<4f41f509-1d44-4476-92b0-9bb643f64576@gmail.com>
Hi Phillip,
Am 09.03.24 um 12:28 schrieb Phillip Wood:
Show 24 quoted lines
> On reflection I don't think I'm objecting to allowing statements,
> only the use of the private functions to do so. If we tweak
> test__run_begin() and test__run_end() so that the description is
> passed to test__run_begin() and we invert the return value of that
> function to match what test__run_end() is expecting then we can have
>
> #define TEST_BEGIN(...)                        \
>     do {                            \
>         int run__ = test__run_begin(__VA_ARGS__);    \
>         if (run__)
>
> #define TEST_END                \
>         test_run_end(run__);        \
>     } while (0)
>
> Which allow test authors to write
>
>     TEST_BEGIN("my test") {
>         /* test body here */
>     } TEST_END;
>
> The macros insulate the test code from having to worry about
> test_skip_all() and the "do { ... } while (0)" means that the
> compiler will complain if the author forgets TEST_END.

the location information is missing, but I get the idea. That would certainly work for t-ctype.

Show 6 quoted lines
> I'm slightly on the fence about including the braces in the macros
> instead as that would make them harder to misuse but it would be less
> obvious that the test body is run in its own block. The compiler will
> allow the test author to accidentally nest two calls to TEST_BEGIN()
> but there is an assertion in test__run_begin() which will catch that
> at run time.

Thought about that as well, and I'd also be wary of including any of the control statements. Custom syntax requires learning, can have weird side-effects and may be misunderstood by editors.

Below is a patch that adds the function test_start() and its companion macro TEST_START, which allow defining tests with minimal ceremony, similarly as in the shell-based test suite:

	#include "test-lib.h"
	int cmd_main(int argc, const char **argv)
	{
		if (TEST_START("something works"))
			check(something());
		if (TEST_START("something else works"))
			check(something_else());
		return test_done();
	}

It requires storing string copies and sits between the states of the original test machinery, so it's a bit complicated.

The biggest downside so far, though, is that I couldn't find an example in the other unit tests that it would simplify significantly. At least it would allow getting rid of the void pointers in t-strbuf, however.

Show 10 quoted lines
> The slight downside compared to TEST() is that it is harder to return
> early if a check fails - we'd need something like
>
>     TEST_BEGIN("my test") {
>         if (!check(0))
>             goto fail
>         /* more checks */
>      fail:
>         ;
>     } TEST_END;

TEST is worse in this regard, as it doesn't allow "if" directly at all. You can use short-circuiting, of course:

	TEST(check(!!ptr) && check(*ptr == value), "ptr points to value");

But you can do that with TEST_BEGIN as well. In a function you can return early, but you can use functions with both, too.

In your example you can use "continue" instead of "goto fail". And with "break" you can skip the test_run_end() call. I consider both to be downsides, though -- the abstraction is leaky.

> Also unlike TEST(), TEST_END does not indicate to the caller whether
> the test failed or not but I'm not sure that matters in practice.
Most TEST invocations out of t-basic ignore its return value so far.

ctx.result is left unchanged by test__run_end(), so we could still access it if really needed. Perhaps through a sanctioned function, last_test_result() or similar.

René
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c
index d6ac1fe678..e7c846814e 100644
--- a/t/unit-tests/t-ctype.c
+++ b/t/unit-tests/t-ctype.c
@@ -4,15 +4,13 @@
 	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_START(#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"); \
 } while (0)

 #define DIGIT "0123456789"
diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c
index 66d6980ffb..bc484c92da 100644
--- a/t/unit-tests/test-lib.c
+++ b/t/unit-tests/test-lib.c
@@ -16,6 +16,8 @@ static struct {
 	unsigned running :1;
 	unsigned skip_all :1;
 	unsigned todo :1;
+	char *desc;
+	char *location;
 } ctx = {
 	.lazy_plan = 1,
 	.result = RESULT_NONE,
@@ -123,9 +125,45 @@ void test_plan(int count)
 	ctx.lazy_plan = 0;
 }

+static void test_run_maybe_end(void)
+{
+	if (ctx.running) {
+		assert(ctx.location);
+		assert(ctx.desc);
+		test__run_end(0, ctx.location, "%s", ctx.desc);
+		FREE_AND_NULL(ctx.location);
+		FREE_AND_NULL(ctx.desc);
+	}
+	assert(!ctx.running);
+	assert(!ctx.location);
+	assert(!ctx.desc);
+}
+
+int test_start(const char *location, const char *format, ...)
+{
+	va_list ap;
+	char *desc;
+
+	test_run_maybe_end();
+
+	va_start(ap, format);
+	desc = xstrvfmt(format, ap);
+	va_end(ap);
+
+	if (test__run_begin()) {
+		test__run_end(1, location, "%s", desc);
+		free(desc);
+		return 0;
+	} else {
+		ctx.location = xstrdup(location);
+		ctx.desc = desc;
+		return 1;
+	}
+}
+
 int test_done(void)
 {
-	assert(!ctx.running);
+	test_run_maybe_end();

 	if (ctx.lazy_plan)
 		test_plan(ctx.count);
diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h
index a8f07ae0b7..2be95b3ab8 100644
--- a/t/unit-tests/test-lib.h
+++ b/t/unit-tests/test-lib.h
@@ -21,6 +21,15 @@
  */
 void test_plan(int count);

+/*
+ * Start a test.  It ends when the next test starts or test_done()
+ * is called.  Returns 1 if the test was actually started, 0 if it was
+ * skipped because test_skip_all() had been called.
+ */
+int test_start(const char *location, const char *format, ...);
+
+#define TEST_START(...) test_start(TEST_LOCATION(), __VA_ARGS__)
+
 /*
  * test_done() must be called at the end of main(). It will print the
  * plan if plan() was not called at the beginning of the test program
Previous: Phillip WoodNext: René Scharfe
Message 28 of 30 in “t-ctype: simplify unit test definitions”
  1. 0/3 t-ctype: simplify unit test definitionsRené Scharfe, Feb 25, 2024
  2. 1/3 t-ctype: allow NUL anywhere in the specification stringRené Scharfe, Feb 25, 2024
  3. Eric SunshineFeb 25, 2024
  4. René ScharfeFeb 25, 2024
  5. Eric SunshineFeb 25, 2024
  6. Jeff KingFeb 25, 2024
  7. Eric SunshineFeb 25, 2024
  8. 2/3 t-ctype: avoid duplicating class namesRené Scharfe, Feb 25, 2024
  9. 3/3 t-ctype: do one test per class and charRené Scharfe, Feb 25, 2024
  10. Christian CouderFeb 26, 2024
  11. René ScharfeFeb 26, 2024
  12. Junio C HamanoFeb 26, 2024
  13. Josh SteadmonFeb 26, 2024
  14. Christian CouderFeb 27, 2024
  15. René ScharfeMar 2, 2024
  16. Christian CouderMar 4, 2024
  17. René ScharfeMar 6, 2024
  18. Josh SteadmonMar 4, 2024
  19. Junio C HamanoMar 4, 2024
  20. 0/4 t-ctype: simplify unit test definitionsRené Scharfe, Mar 3, 2024
  21. 2/4 t-ctype: simplify EOF checkRené Scharfe, Mar 3, 2024
  22. 3/4 t-ctype: align output of iRené Scharfe, Mar 3, 2024
  23. 4/4 t-ctype: avoid duplicating class namesRené Scharfe, Mar 3, 2024
  24. Phillip WoodMar 4, 2024
  25. René ScharfeMar 6, 2024
  26. Phillip WoodMar 8, 2024
  27. Phillip WoodMar 9, 2024
  28. René ScharfeMar 10, 2024
  29. 1/4 t-ctype: allow NUL anywhere in the specification stringRené Scharfe, Mar 3, 2024
  30. Christian CouderMar 4, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.