{"thread":{"id":"61700","subject":"[PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","startedAt":"2024-06-29T15:33:47Z","lastAt":"2024-08-02T00:49:08Z","messageCount":115,"participants":["René Scharfe","Jeff King","Junio C Hamano","Josh Steadmon","phillip.wood123@gmail.com","Ghanshyam Thakkar","Phillip Wood","Kyle Lippincott","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"497838","messageId":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","threadId":"61700","inReplyTo":null,"subject":"[PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:33:38Z","receivedAt":"2024-06-29T15:33:47Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST only allows defining a test that consists of a single\nexpression.  This requires wrapping tests made up of one or more\nstatements in a function, which is a small, but avoidable hurdle.  This\nseries provides a new macro, TEST_RUN, that provides a way to define\ntests without requiring to declare a function.\n\n  t0080: move expected output to a file\n  unit-tests: add TEST_RUN\n  t-ctype: use TEST_RUN\n  t-reftable-basics: use TEST_RUN\n  t-strvec: use TEST_RUN\n  t-strbuf: use TEST_RUN\n\n t/helper/test-example-tap.c      |  33 +++\n t/t0080-unit-test-output.sh      |  48 +----\n t/t0080/expect                   |  76 +++++++\n t/unit-tests/t-ctype.c           |   4 +-\n t/unit-tests/t-reftable-basics.c | 228 +++++++++-----------\n t/unit-tests/t-strbuf.c          |  79 +++----\n t/unit-tests/t-strvec.c          | 356 ++++++++++++++-----------------\n t/unit-tests/test-lib.c          |  42 +++-\n t/unit-tests/test-lib.h          |   8 +\n 9 files changed, 462 insertions(+), 412 deletions(-)\n create mode 100644 t/t0080/expect\n\n--\n2.45.2\n"},{"id":"497839","messageId":"c60c78c7-a029-48e8-840a-45dcc785a6e5@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH 1/6] t0080: move expected output to a file","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:35:31Z","receivedAt":"2024-06-29T15:35:39Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Provide the expected output of \"test-tool example-tap\" verbatim instead\nof as a here-doc, to avoid distractions due to quoting, variables\ncontaining quotes and indentation.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/t0080-unit-test-output.sh | 48 +------------------------------------\n t/t0080/expect              | 43 +++++++++++++++++++++++++++++++++\n 2 files changed, 44 insertions(+), 47 deletions(-)\n create mode 100644 t/t0080/expect\n\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex 7bbb065d58..93f2defa19 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -6,54 +6,8 @@ TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n\n test_expect_success 'TAP output from unit tests' '\n-\tcat >expect <<-EOF &&\n-\tok 1 - passing test\n-\tok 2 - passing test and assertion return 1\n-\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n-\t#    left: 1\n-\t#   right: 2\n-\tnot ok 3 - failing test\n-\tok 4 - failing test and assertion return 0\n-\tnot ok 5 - passing TEST_TODO() # TODO\n-\tok 6 - passing TEST_TODO() returns 1\n-\t# todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26\n-\tnot ok 7 - failing TEST_TODO()\n-\tok 8 - failing TEST_TODO() returns 0\n-\t# check \"0\" failed at t/helper/test-example-tap.c:31\n-\t# skipping test - missing prerequisite\n-\t# skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33\n-\tok 9 - test_skip() # SKIP\n-\tok 10 - skipped test returns 1\n-\t# skipping test - missing prerequisite\n-\tok 11 - test_skip() inside TEST_TODO() # SKIP\n-\tok 12 - test_skip() inside TEST_TODO() returns 1\n-\t# check \"0\" failed at t/helper/test-example-tap.c:49\n-\tnot ok 13 - TEST_TODO() after failing check\n-\tok 14 - TEST_TODO() after failing check returns 0\n-\t# check \"0\" failed at t/helper/test-example-tap.c:57\n-\tnot ok 15 - failing check after TEST_TODO()\n-\tok 16 - failing check after TEST_TODO() returns 0\n-\t# check \"!strcmp(\"\\thello\\\\\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:62\n-\t#    left: \"\\011hello\\\\\\\\\"\n-\t#   right: \"there\\\"\\012\"\n-\t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:63\n-\t#    left: \"NULL\"\n-\t#   right: NULL\n-\t# check \"${SQ}a${SQ} == ${SQ}\\n${SQ}\" failed at t/helper/test-example-tap.c:64\n-\t#    left: ${SQ}a${SQ}\n-\t#   right: ${SQ}\\012${SQ}\n-\t# check \"${SQ}\\\\\\\\${SQ} == ${SQ}\\\\${SQ}${SQ}\" failed at t/helper/test-example-tap.c:65\n-\t#    left: ${SQ}\\\\\\\\${SQ}\n-\t#   right: ${SQ}\\\\${SQ}${SQ}\n-\tnot ok 17 - messages from failing string and char comparison\n-\t# BUG: test has no checks at t/helper/test-example-tap.c:92\n-\tnot ok 18 - test with no checks\n-\tok 19 - test with no checks returns 0\n-\t1..19\n-\tEOF\n-\n \t! test-tool example-tap >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp \"$TEST_DIRECTORY\"/t0080/expect actual\n '\n\n test_done\ndiff --git a/t/t0080/expect b/t/t0080/expect\nnew file mode 100644\nindex 0000000000..0cfa0dc6d8\n--- /dev/null\n+++ b/t/t0080/expect\n@@ -0,0 +1,43 @@\n+ok 1 - passing test\n+ok 2 - passing test and assertion return 1\n+# check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n+#    left: 1\n+#   right: 2\n+not ok 3 - failing test\n+ok 4 - failing test and assertion return 0\n+not ok 5 - passing TEST_TODO() # TODO\n+ok 6 - passing TEST_TODO() returns 1\n+# todo check 'check(x)' succeeded at t/helper/test-example-tap.c:26\n+not ok 7 - failing TEST_TODO()\n+ok 8 - failing TEST_TODO() returns 0\n+# check \"0\" failed at t/helper/test-example-tap.c:31\n+# skipping test - missing prerequisite\n+# skipping check '1' at t/helper/test-example-tap.c:33\n+ok 9 - test_skip() # SKIP\n+ok 10 - skipped test returns 1\n+# skipping test - missing prerequisite\n+ok 11 - test_skip() inside TEST_TODO() # SKIP\n+ok 12 - test_skip() inside TEST_TODO() returns 1\n+# check \"0\" failed at t/helper/test-example-tap.c:49\n+not ok 13 - TEST_TODO() after failing check\n+ok 14 - TEST_TODO() after failing check returns 0\n+# check \"0\" failed at t/helper/test-example-tap.c:57\n+not ok 15 - failing check after TEST_TODO()\n+ok 16 - failing check after TEST_TODO() returns 0\n+# check \"!strcmp(\"\\thello\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:62\n+#    left: \"\\011hello\\\\\"\n+#   right: \"there\\\"\\012\"\n+# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:63\n+#    left: \"NULL\"\n+#   right: NULL\n+# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n+#    left: 'a'\n+#   right: '\\012'\n+# check \"'\\\\' == '\\''\" failed at t/helper/test-example-tap.c:65\n+#    left: '\\\\'\n+#   right: '\\''\n+not ok 17 - messages from failing string and char comparison\n+# BUG: test has no checks at t/helper/test-example-tap.c:92\n+not ok 18 - test with no checks\n+ok 19 - test with no checks returns 0\n+1..19\n--\n2.45.2\n"},{"id":"497840","messageId":"8175f239-8d4e-49f7-ae0d-dba7df8c365d@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH 2/6] unit-tests: add TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:43:30Z","receivedAt":"2024-06-29T15:43:39Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST only allows defining a test that consists of a single\nexpression.  Add the new macro, TEST_RUN, which provides a way to define\nunit tests that are made up of one or more statements.  A test started\nwith it implicitly ends when the next test is started or test_done() is\ncalled.\n\nTEST_RUN allows defining self-contained tests en bloc, a bit like\ntest_expect_success does for regular tests.  Unlike TEST it does not\nrequire defining wrapper functions for test statements.\n\nNo public method is provided for ending a test explicitly, yet; let's\nsee if we'll ever need one.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/helper/test-example-tap.c | 33 +++++++++++++++++++++++++++++\n t/t0080/expect              | 35 ++++++++++++++++++++++++++++++-\n t/unit-tests/test-lib.c     | 42 +++++++++++++++++++++++++++++++++++--\n t/unit-tests/test-lib.h     |  8 +++++++\n 4 files changed, 115 insertions(+), 3 deletions(-)\n\ndiff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\nindex d072ad559f..7b02177a9f 100644\n--- a/t/helper/test-example-tap.c\n+++ b/t/helper/test-example-tap.c\n@@ -92,5 +92,38 @@ int cmd__example_tap(int argc, const char **argv)\n \ttest_res = TEST(t_empty(), \"test with no checks\");\n \tTEST(check_int(test_res, ==, 0), \"test with no checks returns 0\");\n\n+\tif (TEST_RUN(\"TEST_RUN passing test\"))\n+\t\tcheck_int(1, ==, 1);\n+\tif (TEST_RUN(\"TEST_RUN failing test\"))\n+\t\tcheck_int(1, ==, 2);\n+\tif (TEST_RUN(\"TEST_RUN passing TEST_TODO()\"))\n+\t\tTEST_TODO(check(0));\n+\tif (TEST_RUN(\"TEST_RUN failing TEST_TODO()\"))\n+\t\tTEST_TODO(check(1));\n+\tif (TEST_RUN(\"TEST_RUN test_skip()\")) {\n+\t\tcheck(0);\n+\t\ttest_skip(\"missing prerequisite\");\n+\t\tcheck(1);\n+\t}\n+\tif (TEST_RUN(\"TEST_RUN test_skip() inside TEST_TODO()\"))\n+\t\tTEST_TODO((test_skip(\"missing prerequisite\"), 1));\n+\tif (TEST_RUN(\"TEST_RUN TEST_TODO() after failing check\")) {\n+\t\tcheck(0);\n+\t\tTEST_TODO(check(0));\n+\t}\n+\tif (TEST_RUN(\"TEST_RUN failing check after TEST_TODO()\")) {\n+\t\tcheck(1);\n+\t\tTEST_TODO(check(0));\n+\t\tcheck(0);\n+\t}\n+\tif (TEST_RUN(\"TEST_RUN messages from failing string and char comparison\")) {\n+\t\tcheck_str(\"\\thello\\\\\", \"there\\\"\\n\");\n+\t\tcheck_str(\"NULL\", NULL);\n+\t\tcheck_char('a', ==, '\\n');\n+\t\tcheck_char('\\\\', ==, '\\'');\n+\t}\n+\tif (TEST_RUN(\"TEST_RUN test with no checks\"))\n+\t\t; /* nothing */\n+\n \treturn test_done();\n }\ndiff --git a/t/t0080/expect b/t/t0080/expect\nindex 0cfa0dc6d8..92526e1b06 100644\n--- a/t/t0080/expect\n+++ b/t/t0080/expect\n@@ -40,4 +40,37 @@ not ok 17 - messages from failing string and char comparison\n # BUG: test has no checks at t/helper/test-example-tap.c:92\n not ok 18 - test with no checks\n ok 19 - test with no checks returns 0\n-1..19\n+ok 20 - TEST_RUN passing test\n+# check \"1 == 2\" failed at t/helper/test-example-tap.c:98\n+#    left: 1\n+#   right: 2\n+not ok 21 - TEST_RUN failing test\n+not ok 22 - TEST_RUN passing TEST_TODO() # TODO\n+# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:102\n+not ok 23 - TEST_RUN failing TEST_TODO()\n+# check \"0\" failed at t/helper/test-example-tap.c:104\n+# skipping test - missing prerequisite\n+# skipping check '1' at t/helper/test-example-tap.c:106\n+ok 24 - TEST_RUN test_skip() # SKIP\n+# skipping test - missing prerequisite\n+ok 25 - TEST_RUN test_skip() inside TEST_TODO() # SKIP\n+# check \"0\" failed at t/helper/test-example-tap.c:111\n+not ok 26 - TEST_RUN TEST_TODO() after failing check\n+# check \"0\" failed at t/helper/test-example-tap.c:117\n+not ok 27 - TEST_RUN failing check after TEST_TODO()\n+# check \"!strcmp(\"\\thello\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:120\n+#    left: \"\\011hello\\\\\"\n+#   right: \"there\\\"\\012\"\n+# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:121\n+#    left: \"NULL\"\n+#   right: NULL\n+# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:122\n+#    left: 'a'\n+#   right: '\\012'\n+# check \"'\\\\' == '\\''\" failed at t/helper/test-example-tap.c:123\n+#    left: '\\\\'\n+#   right: '\\''\n+not ok 28 - TEST_RUN messages from failing string and char comparison\n+# BUG: test has no checks at t/helper/test-example-tap.c:125\n+not ok 29 - TEST_RUN test with no checks\n+1..29\ndiff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\nindex 3c513ce59a..fc50755fae 100644\n--- a/t/unit-tests/test-lib.c\n+++ b/t/unit-tests/test-lib.c\n@@ -16,6 +16,8 @@ static struct {\n \tunsigned running :1;\n \tunsigned skip_all :1;\n \tunsigned todo :1;\n+\tchar *desc;\n+\tchar *location;\n } ctx = {\n \t.lazy_plan = 1,\n \t.result = RESULT_NONE,\n@@ -123,9 +125,45 @@ void test_plan(int count)\n \tctx.lazy_plan = 0;\n }\n\n-int test_done(void)\n+static void test__run_maybe_end(void)\n {\n+\tif (ctx.running) {\n+\t\tassert(ctx.location);\n+\t\tassert(ctx.desc);\n+\t\ttest__run_end(0, ctx.location, \"%s\", ctx.desc);\n+\t\tFREE_AND_NULL(ctx.location);\n+\t\tFREE_AND_NULL(ctx.desc);\n+\t}\n \tassert(!ctx.running);\n+\tassert(!ctx.location);\n+\tassert(!ctx.desc);\n+}\n+\n+int test__run(const char *location, const char *format, ...)\n+{\n+\tva_list ap;\n+\tchar *desc;\n+\n+\ttest__run_maybe_end();\n+\n+\tva_start(ap, format);\n+\tdesc = xstrvfmt(format, ap);\n+\tva_end(ap);\n+\n+\tif (test__run_begin()) {\n+\t\ttest__run_end(1, location, \"%s\", desc);\n+\t\tfree(desc);\n+\t\treturn 0;\n+\t} else {\n+\t\tctx.location = xstrdup(location);\n+\t\tctx.desc = desc;\n+\t\treturn 1;\n+\t}\n+}\n+\n+int test_done(void)\n+{\n+\ttest__run_maybe_end();\n\n \tif (ctx.lazy_plan)\n \t\ttest_plan(ctx.count);\n@@ -169,7 +207,7 @@ void test_skip_all(const char *format, ...)\n\n int test__run_begin(void)\n {\n-\tassert(!ctx.running);\n+\ttest__run_maybe_end();\n\n \tctx.count++;\n \tctx.result = RESULT_NONE;\ndiff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h\nindex 2de6d715d5..6df40e3b12 100644\n--- a/t/unit-tests/test-lib.h\n+++ b/t/unit-tests/test-lib.h\n@@ -21,6 +21,13 @@\n  */\n void test_plan(int count);\n\n+/*\n+ * Start a test, returns 1 if the test was actually started or 0 if it\n+ * was skipped.  The test ends when the next test starts or test_done()\n+ * is called.\n+ */\n+#define TEST_RUN(...) test__run(TEST_LOCATION(), __VA_ARGS__)\n+\n /*\n  * test_done() must be called at the end of main(). It will print the\n  * plan if plan() was not called at the beginning of the test program\n@@ -156,6 +163,7 @@ extern union test__tmp test__tmp[2];\n int test__run_begin(void);\n __attribute__((format (printf, 3, 4)))\n int test__run_end(int, const char *, const char *, ...);\n+int test__run(const char *location, const char *format, ...);\n void test__todo_begin(void);\n int test__todo_end(const char *, const char *, int);\n\n--\n2.45.2\n"},{"id":"497841","messageId":"abb96bfc-ed97-4c8e-a99a-0dadce45398e@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH 3/6] t-ctype: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:44:18Z","receivedAt":"2024-06-29T15:44:26Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Use the macro TEST_RUN instead of the internal functions\ntest__run_begin() and test__run_end().\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-ctype.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c\nindex d6ac1fe678..04a66dc105 100644\n--- a/t/unit-tests/t-ctype.c\n+++ b/t/unit-tests/t-ctype.c\n@@ -4,15 +4,13 @@\n \tsize_t len = ARRAY_SIZE(string) - 1 + \\\n \t\tBUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \\\n \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n-\tint skip = test__run_begin(); \\\n-\tif (!skip) { \\\n+\tif (TEST_RUN(#class \" works\")) { \\\n \t\tfor (int i = 0; i < 256; i++) { \\\n \t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n \t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n \t\t} \\\n \t\tcheck(!class(EOF)); \\\n \t} \\\n-\ttest__run_end(!skip, TEST_LOCATION(), #class \" works\"); \\\n } while (0)\n\n #define DIGIT \"0123456789\"\n--\n2.45.2\n"},{"id":"497842","messageId":"16223dec-c117-4f8f-958f-bfc407882b4b@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH 4/6] t-reftable-basics: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:45:56Z","receivedAt":"2024-06-29T15:46:03Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the overhead of defining and calling single-use functions by\nusing TEST_RUN instead.\n\nRun the tests in the order of definition.  We can reorder them like that\nbecause they are independent.  Technically this changes the output, but\nretains the meaning of a full run and allows for easier review e.g. with\ndiff option --ignore-all-space.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-reftable-basics.c | 228 ++++++++++++++-----------------\n 1 file changed, 106 insertions(+), 122 deletions(-)\n\ndiff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c\nindex 4e80bdf16d..e6e789e741 100644\n--- a/t/unit-tests/t-reftable-basics.c\n+++ b/t/unit-tests/t-reftable-basics.c\n@@ -20,141 +20,125 @@ static int integer_needle_lesseq(size_t i, void *_args)\n \treturn args->needle <= args->haystack[i];\n }\n\n-static void test_binsearch(void)\n+int cmd_main(int argc, const char *argv[])\n {\n-\tint haystack[] = { 2, 4, 6, 8, 10 };\n-\tstruct {\n-\t\tint needle;\n-\t\tsize_t expected_idx;\n-\t} testcases[] = {\n-\t\t{-9000, 0},\n-\t\t{-1, 0},\n-\t\t{0, 0},\n-\t\t{2, 0},\n-\t\t{3, 1},\n-\t\t{4, 1},\n-\t\t{7, 3},\n-\t\t{9, 4},\n-\t\t{10, 4},\n-\t\t{11, 5},\n-\t\t{9000, 5},\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n-\t\tstruct integer_needle_lesseq_args args = {\n-\t\t\t.haystack = haystack,\n-\t\t\t.needle = testcases[i].needle,\n+\tif (TEST_RUN(\"binary search with binsearch works\")) {\n+\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n+\t\tstruct {\n+\t\t\tint needle;\n+\t\t\tsize_t expected_idx;\n+\t\t} testcases[] = {\n+\t\t\t{-9000, 0},\n+\t\t\t{-1, 0},\n+\t\t\t{0, 0},\n+\t\t\t{2, 0},\n+\t\t\t{3, 1},\n+\t\t\t{4, 1},\n+\t\t\t{7, 3},\n+\t\t\t{9, 4},\n+\t\t\t{10, 4},\n+\t\t\t{11, 5},\n+\t\t\t{9000, 5},\n \t\t};\n-\t\tsize_t idx;\n\n-\t\tidx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args);\n-\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n+\t\t\tstruct integer_needle_lesseq_args args = {\n+\t\t\t\t.haystack = haystack,\n+\t\t\t\t.needle = testcases[i].needle,\n+\t\t\t};\n+\t\t\tsize_t idx;\n+\n+\t\t\tidx = binsearch(ARRAY_SIZE(haystack),\n+\t\t\t\t\t&integer_needle_lesseq, &args);\n+\t\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\t}\n \t}\n-}\n\n-static void test_names_length(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", NULL };\n-\tcheck_int(names_length(a), ==, 2);\n-}\n-\n-static void test_names_equal(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tconst char *c[] = { \"a\", \"b\", NULL };\n+\tif (TEST_RUN(\"names_length retuns size of a NULL-terminated string array\")) {\n+\t\tconst char *a[] = { \"a\", \"b\", NULL };\n+\t\tcheck_int(names_length(a), ==, 2);\n+\t}\n\n-\tcheck(names_equal(a, a));\n-\tcheck(!names_equal(a, b));\n-\tcheck(!names_equal(a, c));\n-}\n+\tif (TEST_RUN(\"names_equal compares NULL-terminated string arrays\")) {\n+\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\t\tconst char *c[] = { \"a\", \"b\", NULL };\n\n-static void test_parse_names_normal(void)\n-{\n-\tchar in1[] = \"line\\n\";\n-\tchar in2[] = \"a\\nb\\nc\";\n-\tchar **out = NULL;\n-\tparse_names(in1, strlen(in1), &out);\n-\tcheck_str(out[0], \"line\");\n-\tcheck(!out[1]);\n-\tfree_names(out);\n-\n-\tparse_names(in2, strlen(in2), &out);\n-\tcheck_str(out[0], \"a\");\n-\tcheck_str(out[1], \"b\");\n-\tcheck_str(out[2], \"c\");\n-\tcheck(!out[3]);\n-\tfree_names(out);\n-}\n+\t\tcheck(names_equal(a, a));\n+\t\tcheck(!names_equal(a, b));\n+\t\tcheck(!names_equal(a, c));\n+\t}\n\n-static void test_parse_names_drop_empty(void)\n-{\n-\tchar in[] = \"a\\n\\nb\\n\";\n-\tchar **out = NULL;\n-\tparse_names(in, strlen(in), &out);\n-\tcheck_str(out[0], \"a\");\n-\t/* simply '\\n' should be dropped as empty string */\n-\tcheck_str(out[1], \"b\");\n-\tcheck(!out[2]);\n-\tfree_names(out);\n-}\n+\tif (TEST_RUN(\"parse_names works for basic input\")) {\n+\t\tchar in1[] = \"line\\n\";\n+\t\tchar in2[] = \"a\\nb\\nc\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in1, strlen(in1), &out);\n+\t\tcheck_str(out[0], \"line\");\n+\t\tcheck(!out[1]);\n+\t\tfree_names(out);\n+\n+\t\tparse_names(in2, strlen(in2), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck_str(out[2], \"c\");\n+\t\tcheck(!out[3]);\n+\t\tfree_names(out);\n+\t}\n\n-static void test_common_prefix(void)\n-{\n-\tstruct strbuf a = STRBUF_INIT;\n-\tstruct strbuf b = STRBUF_INIT;\n-\tstruct {\n-\t\tconst char *a, *b;\n-\t\tint want;\n-\t} cases[] = {\n-\t\t{\"abcdef\", \"abc\", 3},\n-\t\t{ \"abc\", \"ab\", 2 },\n-\t\t{ \"\", \"abc\", 0 },\n-\t\t{ \"abc\", \"abd\", 2 },\n-\t\t{ \"abc\", \"pqr\", 0 },\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n-\t\tstrbuf_addstr(&a, cases[i].a);\n-\t\tstrbuf_addstr(&b, cases[i].b);\n-\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n-\t\tstrbuf_reset(&a);\n-\t\tstrbuf_reset(&b);\n+\tif (TEST_RUN(\"parse_names drops empty string\")) {\n+\t\tchar in[] = \"a\\n\\nb\\n\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in, strlen(in), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\t/* simply '\\n' should be dropped as empty string */\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck(!out[2]);\n+\t\tfree_names(out);\n \t}\n-\tstrbuf_release(&a);\n-\tstrbuf_release(&b);\n-}\n\n-static void test_u24_roundtrip(void)\n-{\n-\tuint32_t in = 0x112233;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be24(dest, in);\n-\tout = get_be24(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\tif (TEST_RUN(\"common_prefix_size works\")) {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstruct {\n+\t\t\tconst char *a, *b;\n+\t\t\tint want;\n+\t\t} cases[] = {\n+\t\t\t{\"abcdef\", \"abc\", 3},\n+\t\t\t{ \"abc\", \"ab\", 2 },\n+\t\t\t{ \"\", \"abc\", 0 },\n+\t\t\t{ \"abc\", \"abd\", 2 },\n+\t\t\t{ \"abc\", \"pqr\", 0 },\n+\t\t};\n\n-static void test_u16_roundtrip(void)\n-{\n-\tuint32_t in = 0xfef1;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be16(dest, in);\n-\tout = get_be16(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n+\t\t\tstrbuf_reset(&a);\n+\t\t\tstrbuf_reset(&b);\n+\t\t}\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n\n-int cmd_main(int argc, const char *argv[])\n-{\n-\tTEST(test_common_prefix(), \"common_prefix_size works\");\n-\tTEST(test_parse_names_normal(), \"parse_names works for basic input\");\n-\tTEST(test_parse_names_drop_empty(), \"parse_names drops empty string\");\n-\tTEST(test_binsearch(), \"binary search with binsearch works\");\n-\tTEST(test_names_length(), \"names_length retuns size of a NULL-terminated string array\");\n-\tTEST(test_names_equal(), \"names_equal compares NULL-terminated string arrays\");\n-\tTEST(test_u24_roundtrip(), \"put_be24 and get_be24 work\");\n-\tTEST(test_u16_roundtrip(), \"put_be16 and get_be16 work\");\n+\tif (TEST_RUN(\"put_be24 and get_be24 work\")) {\n+\t\tuint32_t in = 0x112233;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be24(dest, in);\n+\t\tout = get_be24(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n+\n+\tif (TEST_RUN(\"put_be16 and get_be16 work\")) {\n+\t\tuint32_t in = 0xfef1;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be16(dest, in);\n+\t\tout = get_be16(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"497843","messageId":"27cdab5f-15b3-45fb-89e1-530a6c0817a6@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH 5/6] t-strvec: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:46:48Z","receivedAt":"2024-06-29T15:46:57Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the overhead of defining and calling single-use functions by\nusing TEST_RUN instead.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strvec.c | 356 ++++++++++++++++++----------------------\n 1 file changed, 156 insertions(+), 200 deletions(-)\n\ndiff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c\nindex d4615ab06d..00ff7d4ae8 100644\n--- a/t/unit-tests/t-strvec.c\n+++ b/t/unit-tests/t-strvec.c\n@@ -36,237 +36,193 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n \tcheck_pointer_eq(vec->v[nr], NULL);\n }\n\n-static void t_static_init(void)\n+int cmd_main(int argc, const char **argv)\n {\n-\tstruct strvec vec = STRVEC_INIT;\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tif (TEST_RUN(\"static initialization\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_dynamic_init(void)\n-{\n-\tstruct strvec vec;\n-\tstrvec_init(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tif (TEST_RUN(\"dynamic initialization\")) {\n+\t\tstruct strvec vec;\n+\t\tstrvec_init(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_clear(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_push(&vec, \"foo\");\n-\tstrvec_clear(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tif (TEST_RUN(\"clear\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_clear(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_push(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n+\tif (TEST_RUN(\"push\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_push(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n\n-\tstrvec_push(&vec, \"bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_push(&vec, \"bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushf(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushf(&vec, \"foo: %d\", 1);\n-\tcheck_strvec(&vec, \"foo: 1\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"pushf\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n+\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushl(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"pushl\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushv(void)\n-{\n-\tconst char *strings[] = {\n-\t\t\"foo\", \"bar\", \"baz\", NULL,\n-\t};\n-\tstruct strvec vec = STRVEC_INIT;\n+\tif (TEST_RUN(\"pushv\")) {\n+\t\tconst char *strings[] = {\n+\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n+\t\t};\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_pushv(&vec, strings);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pushv(&vec, strings);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 0, \"replaced\");\n-\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"replace at head\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 0, \"replaced\");\n+\t\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 2, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"replace at tail\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 2, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 1, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"replace in between\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 1, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_with_substring(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", NULL);\n-\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n-\tcheck_strvec(&vec, \"oo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"replace with substring\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", NULL);\n+\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n+\t\tcheck_strvec(&vec, \"oo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 0);\n-\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"remove at head\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 0);\n+\t\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 2);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"remove at tail\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 2);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 1);\n-\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"remove in between\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 1);\n+\t\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"pop with empty array\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_non_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"pop with non-empty array\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_empty_string(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"split empty string\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_single_item(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"split single item\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_items(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo bar baz\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"split multiple items\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo bar baz\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_whitespace_only(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \" \\t\\n\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"split whitespace only\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \" \\t\\n\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_consecutive_whitespaces(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\\n\\t bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif (TEST_RUN(\"split multiple consecutive whitespaces\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_detach(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tconst char **detached;\n+\tif (TEST_RUN(\"detach\")) {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tconst char **detached;\n\n-\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_push(&vec, \"foo\");\n\n-\tdetached = strvec_detach(&vec);\n-\tcheck_str(detached[0], \"foo\");\n-\tcheck_pointer_eq(detached[1], NULL);\n+\t\tdetached = strvec_detach(&vec);\n+\t\tcheck_str(detached[0], \"foo\");\n+\t\tcheck_pointer_eq(detached[1], NULL);\n\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n\n-\tfree((char *) detached[0]);\n-\tfree(detached);\n-}\n+\t\tfree((char *) detached[0]);\n+\t\tfree(detached);\n+\t}\n\n-int cmd_main(int argc, const char **argv)\n-{\n-\tTEST(t_static_init(), \"static initialization\");\n-\tTEST(t_dynamic_init(), \"dynamic initialization\");\n-\tTEST(t_clear(), \"clear\");\n-\tTEST(t_push(), \"push\");\n-\tTEST(t_pushf(), \"pushf\");\n-\tTEST(t_pushl(), \"pushl\");\n-\tTEST(t_pushv(), \"pushv\");\n-\tTEST(t_replace_at_head(), \"replace at head\");\n-\tTEST(t_replace_in_between(), \"replace in between\");\n-\tTEST(t_replace_at_tail(), \"replace at tail\");\n-\tTEST(t_replace_with_substring(), \"replace with substring\");\n-\tTEST(t_remove_at_head(), \"remove at head\");\n-\tTEST(t_remove_in_between(), \"remove in between\");\n-\tTEST(t_remove_at_tail(), \"remove at tail\");\n-\tTEST(t_pop_empty_array(), \"pop with empty array\");\n-\tTEST(t_pop_non_empty_array(), \"pop with non-empty array\");\n-\tTEST(t_split_empty_string(), \"split empty string\");\n-\tTEST(t_split_single_item(), \"split single item\");\n-\tTEST(t_split_multiple_items(), \"split multiple items\");\n-\tTEST(t_split_whitespace_only(), \"split whitespace only\");\n-\tTEST(t_split_multiple_consecutive_whitespaces(), \"split multiple consecutive whitespaces\");\n-\tTEST(t_detach(), \"detach\");\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"497844","messageId":"1bf053ae-957e-4e9a-90f0-11cc76848ce9@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-06-29T15:47:34Z","receivedAt":"2024-06-29T15:47:42Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.  The functions setup() and setup_populated() here\nare used for that purpose and take another function as an argument,\nmaking the control flow hard to follow.\n\nRemove the overhead of these functions by using TEST_RUN instead.  Move\ntheir duplicate post-condition checks into a new helper, t_release(),\nand let t_addch() and t_addstr() accept properly typed input parameters\ninstead of void pointers.\n\nUse the fully checking t_addstr() for adding initial values instead of\nonly doing only a length comparison -- there's no need for skipping the\nother checks.\n\nThis results in test cases that look much more like strbuf usage in\nproduction code, only with checked strbuf functions replaced by checking\nwrappers.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strbuf.c | 79 +++++++++++++++++++++--------------------\n 1 file changed, 41 insertions(+), 38 deletions(-)\n\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex 6027dafef7..c8e39ddda7 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -1,32 +1,6 @@\n #include \"test-lib.h\"\n #include \"strbuf.h\"\n\n-/* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, const void*),\n-\t\t  const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n-/* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, const void*),\n-\t\t\t    const char *init_str, const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tstrbuf_addstr(&buf, init_str);\n-\tcheck_uint(buf.len, ==, strlen(init_str));\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n static int assert_sane_strbuf(struct strbuf *buf)\n {\n \t/* Initialized strbufs should always have a non-NULL buffer */\n@@ -66,10 +40,8 @@ static void t_dynamic_init(void)\n \tstrbuf_release(&buf);\n }\n\n-static void t_addch(struct strbuf *buf, const void *data)\n+static void t_addch(struct strbuf *buf, int ch)\n {\n-\tconst char *p_ch = data;\n-\tconst char ch = *p_ch;\n \tsize_t orig_alloc = buf->alloc;\n \tsize_t orig_len = buf->len;\n\n@@ -85,9 +57,8 @@ static void t_addch(struct strbuf *buf, const void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n\n-static void t_addstr(struct strbuf *buf, const void *data)\n+static void t_addstr(struct strbuf *buf, const char *text)\n {\n-\tconst char *text = data;\n \tsize_t len = strlen(text);\n \tsize_t orig_alloc = buf->alloc;\n \tsize_t orig_len = buf->len;\n@@ -105,18 +76,50 @@ static void t_addstr(struct strbuf *buf, const void *data)\n \tcheck_str(buf->buf + orig_len, text);\n }\n\n+static void t_release(struct strbuf *sb)\n+{\n+\tstrbuf_release(sb);\n+\tcheck_uint(sb->len, ==, 0);\n+\tcheck_uint(sb->alloc, ==, 0);\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n \tif (!TEST(t_static_init(), \"static initialization works\"))\n \t\ttest_skip_all(\"STRBUF_INIT is broken\");\n \tTEST(t_dynamic_init(), \"dynamic initialization works\");\n-\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n-\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n-\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n-\t     \"strbuf_addch appends to initial value\");\n-\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n-\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n-\t     \"strbuf_addstr appends string to initial value\");\n+\n+\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addch(&sb, 'a');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addch(&sb, '\\0');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addstr(&sb, \"initial value\");\n+\t\tt_addch(&sb, 'a');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addstr(&sb, \"hello there\");\n+\t\tt_release(&sb);\n+\t}\n+\n+\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addstr(&sb, \"initial value\");\n+\t\tt_addstr(&sb, \"hello there\");\n+\t\tt_release(&sb);\n+\t}\n\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"497870","messageId":"20240701032047.GA610406@coredump.intra.peff.net","threadId":"61700","inReplyTo":"c60c78c7-a029-48e8-840a-45dcc785a6e5@web.de","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-07-01T03:20:47Z","receivedAt":"2024-07-01T03:20:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sat, Jun 29, 2024 at 05:35:31PM +0200, René Scharfe wrote:\n\n> Provide the expected output of \"test-tool example-tap\" verbatim instead\n> of as a here-doc, to avoid distractions due to quoting, variables\n> containing quotes and indentation.\n\nI'm not really opposed to this patch, but I wondered...\n\n>  test_expect_success 'TAP output from unit tests' '\n> -\tcat >expect <<-EOF &&\n> -\tok 1 - passing test\n> -\tok 2 - passing test and assertion return 1\n\nIf you could take the test input on stdin, like so:\n\n  test_expect_success 'TAP output from unit tests' - <<-\\EOT\n\tcat >expect <<-\\EOF\n\tok 1 - passing test\n\tok 2 - passing test and assertion return 1\n\t[...]\n\t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n\t#    left: 'a'\n\t#   right: '\\012'\n\t[...]\n\tEOF\n  EOT\n\nwould that be preferable to moving it to its own file? I kind of like\nkeeping everything in the test scripts themselves so related changes can\nhappen side-by-side, though I admit in this case it is intimately tied\nto the separate test-example-tap.c source anyway.\n\nBut I do have such an \"EOT\" patch which I've been meaning to send out,\nsince it makes many of these quoting annoyances go away (though of\ncourse it leaves the indentation).\n\n-Peff\n"},{"id":"497916","messageId":"xmqqed8cx6q0.fsf@gitster.g","threadId":"61700","inReplyTo":"20240701032047.GA610406@coredump.intra.peff.net","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-01T19:17:11Z","receivedAt":"2024-07-01T19:17:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Sat, Jun 29, 2024 at 05:35:31PM +0200, René Scharfe wrote:\n>\n>> Provide the expected output of \"test-tool example-tap\" verbatim instead\n>> of as a here-doc, to avoid distractions due to quoting, variables\n>> containing quotes and indentation.\n>\n> I'm not really opposed to this patch, but I wondered...\n>\n>>  test_expect_success 'TAP output from unit tests' '\n>> -\tcat >expect <<-EOF &&\n>> -\tok 1 - passing test\n>> -\tok 2 - passing test and assertion return 1\n>\n> If you could take the test input on stdin, like so:\n>\n>   test_expect_success 'TAP output from unit tests' - <<-\\EOT\n> \tcat >expect <<-\\EOF\n> \tok 1 - passing test\n> \tok 2 - passing test and assertion return 1\n> \t[...]\n> \t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n> \t#    left: 'a'\n> \t#   right: '\\012'\n> \t[...]\n> \tEOF\n>   EOT\n>\n> would that be preferable to moving it to its own file? I kind of like\n> keeping everything in the test scripts themselves so related changes can\n> happen side-by-side, though I admit in this case it is intimately tied\n> to the separate test-example-tap.c source anyway.\n\nYeah, it does feel a bit of cop-out to separate the expectation out\nto an external file.  I guess I was to blame for things like t4013\nbut there is a valid excuse there (it would be expected that similar\ntests would need to be added and one test per one expected result was\na natural way to manage hundreds of tests).\n\nIn this case, I think the fact that validating the test framework is\nan oddball use case is a sufficient excuse ;-).\n\n> But I do have such an \"EOT\" patch which I've been meaning to send out,\n> since it makes many of these quoting annoyances go away (though of\n> course it leaves the indentation).\n\nI am not sure about your \"test body comes from the standard input\"\n(not saying \"I am not convinced it is a good idea\" or even \"I am\nconvinced it is a bad idea\"---I do not know what to think about it,\nnot just yet).  THe above illustration does make it easier to grok\nby keeping everything in one place.\n\nThanks.\n"},{"id":"497923","messageId":"eh556xjlxipeyumm3gchiiggyogpcuznil5kwvudcpyecj4uxq@uy5tk66blzsl","threadId":"61700","inReplyTo":"abb96bfc-ed97-4c8e-a99a-0dadce45398e@web.de","subject":"Re: [PATCH 3/6] t-ctype: use TEST_RUN","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-07-01T19:49:21Z","receivedAt":"2024-07-01T19:49:27Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.06.29 17:44, René Scharfe wrote:\n> Use the macro TEST_RUN instead of the internal functions\n> test__run_begin() and test__run_end().\n> \n> Signed-off-by: René Scharfe <l.s.r@web.de>\n\nNitpick: please expand the commit message here as you did for the\nfollowing patches.\n"},{"id":"497924","messageId":"177e98da-803e-4839-9484-fd24828bd21b@web.de","threadId":"61700","inReplyTo":"20240701032047.GA610406@coredump.intra.peff.net","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-01T19:51:31Z","receivedAt":"2024-07-01T19:51:47Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 01.07.24 um 05:20 schrieb Jeff King:\n> On Sat, Jun 29, 2024 at 05:35:31PM +0200, René Scharfe wrote:\n>\n>> Provide the expected output of \"test-tool example-tap\" verbatim instead\n>> of as a here-doc, to avoid distractions due to quoting, variables\n>> containing quotes and indentation.\n>\n> I'm not really opposed to this patch, but I wondered...\n>\n>>  test_expect_success 'TAP output from unit tests' '\n>> -\tcat >expect <<-EOF &&\n>> -\tok 1 - passing test\n>> -\tok 2 - passing test and assertion return 1\n>\n> If you could take the test input on stdin, like so:\n>\n>   test_expect_success 'TAP output from unit tests' - <<-\\EOT\n> \tcat >expect <<-\\EOF\n> \tok 1 - passing test\n> \tok 2 - passing test and assertion return 1\n> \t[...]\n> \t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n> \t#    left: 'a'\n> \t#   right: '\\012'\n> \t[...]\n> \tEOF\n>   EOT\n>\n> would that be preferable to moving it to its own file? I kind of like\n> keeping everything in the test scripts themselves so related changes can\n> happen side-by-side, though I admit in this case it is intimately tied\n> to the separate test-example-tap.c source anyway.\n\nI can't think of an example where we keep test definitions in the same\nfile as the code to be tested.  It would be somewhat cool to empower the\nunit test framework to test itself, but I suspect that this nesting\nability would be hard to achieve and not very useful otherwise.  And\nwould we be able to trust such a self-test?\n\n\nWe could cheese it by putting the expected output into a special comment\nbefore (or after) the TEST invocations and letting the test script piece\nthem together to build the expect file, something like:\n\n\t/* expect\n\tok 1 - passing test\n\t */\n\ttest_res = TEST(check_res = check_int(1, ==, 1), \"passing test\");\n\nThat would be a bit annoying if we change something because some\nmessages contain line numbers and the comments would affect those due\nto their existence alone.  And there would be a ripple effect if we\nchange the number of output lines of a test to the output of later\ntests.\n\n\nThe only downside of keeping the expected output of t0080 separate that\nI can think of is that it might get confusing if we'd ever add more\ntest_expect_success calls to it, but I can't imagine why we'd want to\ndo that.\n\n> But I do have such an \"EOT\" patch which I've been meaning to send out,\n> since it makes many of these quoting annoyances go away (though of\n> course it leaves the indentation).\n\nBeing able to pass the test code to test_expect_success as a here-doc or\nfile to avoid nested shell quoting sounds useful in general.  For t0080\nwe could achieve the same effect already by creating the expect file\nbefore calling test_expect_success.  That has the downside of passing\neven when the disk is full and the files are created empty, but we can\nthrow in a \"test -s\" to rule it out.\n\nRené\n"},{"id":"497925","messageId":"bh5ectgtwdaeltflggzwnwhobewvcqqelforr4sfcmq47rnnrl@mzspptlhpofx","threadId":"61700","inReplyTo":"1bf053ae-957e-4e9a-90f0-11cc76848ce9@web.de","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-07-01T19:58:24Z","receivedAt":"2024-07-01T19:58:30Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.06.29 17:47, René Scharfe wrote:\n> The macro TEST takes a single expression.  If a test requires multiple\n> statements then they need to be placed in a function that's called in\n> the TEST expression.  The functions setup() and setup_populated() here\n> are used for that purpose and take another function as an argument,\n> making the control flow hard to follow.\n> \n> Remove the overhead of these functions by using TEST_RUN instead.  Move\n> their duplicate post-condition checks into a new helper, t_release(),\n> and let t_addch() and t_addstr() accept properly typed input parameters\n> instead of void pointers.\n> \n> Use the fully checking t_addstr() for adding initial values instead of\n> only doing only a length comparison -- there's no need for skipping the\n> other checks.\n> \n> This results in test cases that look much more like strbuf usage in\n> production code, only with checked strbuf functions replaced by checking\n> wrappers.\n> \n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  t/unit-tests/t-strbuf.c | 79 +++++++++++++++++++++--------------------\n>  1 file changed, 41 insertions(+), 38 deletions(-)\n> \n> diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\n> index 6027dafef7..c8e39ddda7 100644\n> --- a/t/unit-tests/t-strbuf.c\n> +++ b/t/unit-tests/t-strbuf.c\n> @@ -1,32 +1,6 @@\n>  #include \"test-lib.h\"\n>  #include \"strbuf.h\"\n> \n> -/* wrapper that supplies tests with an empty, initialized strbuf */\n> -static void setup(void (*f)(struct strbuf*, const void*),\n> -\t\t  const void *data)\n> -{\n> -\tstruct strbuf buf = STRBUF_INIT;\n> -\n> -\tf(&buf, data);\n> -\tstrbuf_release(&buf);\n> -\tcheck_uint(buf.len, ==, 0);\n> -\tcheck_uint(buf.alloc, ==, 0);\n> -}\n> -\n> -/* wrapper that supplies tests with a populated, initialized strbuf */\n> -static void setup_populated(void (*f)(struct strbuf*, const void*),\n> -\t\t\t    const char *init_str, const void *data)\n> -{\n> -\tstruct strbuf buf = STRBUF_INIT;\n> -\n> -\tstrbuf_addstr(&buf, init_str);\n> -\tcheck_uint(buf.len, ==, strlen(init_str));\n> -\tf(&buf, data);\n> -\tstrbuf_release(&buf);\n> -\tcheck_uint(buf.len, ==, 0);\n> -\tcheck_uint(buf.alloc, ==, 0);\n> -}\n> -\n>  static int assert_sane_strbuf(struct strbuf *buf)\n>  {\n>  \t/* Initialized strbufs should always have a non-NULL buffer */\n> @@ -66,10 +40,8 @@ static void t_dynamic_init(void)\n>  \tstrbuf_release(&buf);\n>  }\n> \n> -static void t_addch(struct strbuf *buf, const void *data)\n> +static void t_addch(struct strbuf *buf, int ch)\n>  {\n> -\tconst char *p_ch = data;\n> -\tconst char ch = *p_ch;\n>  \tsize_t orig_alloc = buf->alloc;\n>  \tsize_t orig_len = buf->len;\n> \n> @@ -85,9 +57,8 @@ static void t_addch(struct strbuf *buf, const void *data)\n>  \tcheck_char(buf->buf[buf->len], ==, '\\0');\n>  }\n> \n> -static void t_addstr(struct strbuf *buf, const void *data)\n> +static void t_addstr(struct strbuf *buf, const char *text)\n>  {\n> -\tconst char *text = data;\n>  \tsize_t len = strlen(text);\n>  \tsize_t orig_alloc = buf->alloc;\n>  \tsize_t orig_len = buf->len;\n> @@ -105,18 +76,50 @@ static void t_addstr(struct strbuf *buf, const void *data)\n>  \tcheck_str(buf->buf + orig_len, text);\n>  }\n> \n> +static void t_release(struct strbuf *sb)\n> +{\n> +\tstrbuf_release(sb);\n> +\tcheck_uint(sb->len, ==, 0);\n> +\tcheck_uint(sb->alloc, ==, 0);\n> +}\n> +\n>  int cmd_main(int argc, const char **argv)\n>  {\n>  \tif (!TEST(t_static_init(), \"static initialization works\"))\n>  \t\ttest_skip_all(\"STRBUF_INIT is broken\");\n>  \tTEST(t_dynamic_init(), \"dynamic initialization works\");\n\nIIUC you're leaving t_static_init() as-is so that we can determine\nwhether or not to skip the rest of the tests, but is there a reason you\ndidn't convert t_dynamic_init() here?\n\n> -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n> -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n> -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> -\t     \"strbuf_addch appends to initial value\");\n> -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n> -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n> -\t     \"strbuf_addstr appends string to initial value\");\n> +\n> +\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tt_addch(&sb, 'a');\n> +\t\tt_release(&sb);\n> +\t}\n> +\n> +\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tt_addch(&sb, '\\0');\n> +\t\tt_release(&sb);\n> +\t}\n> +\n> +\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tt_addstr(&sb, \"initial value\");\n> +\t\tt_addch(&sb, 'a');\n> +\t\tt_release(&sb);\n> +\t}\n> +\n> +\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tt_addstr(&sb, \"hello there\");\n> +\t\tt_release(&sb);\n> +\t}\n> +\n> +\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tt_addstr(&sb, \"initial value\");\n> +\t\tt_addstr(&sb, \"hello there\");\n> +\t\tt_release(&sb);\n> +\t}\n> \n>  \treturn test_done();\n>  }\n> --\n> 2.45.2\n\nI think this commit in particular shows how TEST_RUN() is more\nconvenient than TEST(). (Although, arguably we shouldn't have allowed\nthe setup() + callback situation to start with.)\n"},{"id":"497926","messageId":"e2mbra55rl2cl746o7knmyczyxrkr3p2u5kacfz4x4e4enfdcs@4fudcpcjtq2b","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"Re: [PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-07-01T19:59:44Z","receivedAt":"2024-07-01T19:59:50Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.06.29 17:33, René Scharfe wrote:\n> The macro TEST only allows defining a test that consists of a single\n> expression.  This requires wrapping tests made up of one or more\n> statements in a function, which is a small, but avoidable hurdle.  This\n> series provides a new macro, TEST_RUN, that provides a way to define\n> tests without requiring to declare a function.\n> \n>   t0080: move expected output to a file\n>   unit-tests: add TEST_RUN\n>   t-ctype: use TEST_RUN\n>   t-reftable-basics: use TEST_RUN\n>   t-strvec: use TEST_RUN\n>   t-strbuf: use TEST_RUN\n> \n>  t/helper/test-example-tap.c      |  33 +++\n>  t/t0080-unit-test-output.sh      |  48 +----\n>  t/t0080/expect                   |  76 +++++++\n>  t/unit-tests/t-ctype.c           |   4 +-\n>  t/unit-tests/t-reftable-basics.c | 228 +++++++++-----------\n>  t/unit-tests/t-strbuf.c          |  79 +++----\n>  t/unit-tests/t-strvec.c          | 356 ++++++++++++++-----------------\n>  t/unit-tests/test-lib.c          |  42 +++-\n>  t/unit-tests/test-lib.h          |   8 +\n>  9 files changed, 462 insertions(+), 412 deletions(-)\n>  create mode 100644 t/t0080/expect\n> \n> --\n> 2.45.2\n\nOne small nitpick on patch #3 and a question on #6, but basically this\nseries looks good to me. I'll be away from email for the rest of the\nweek, so I'll go ahead and sign off:\n\nReviewed-by: Josh Steadmon <steadmon@google.com>\n"},{"id":"497927","messageId":"1a9c7351-68c2-4f2a-b00f-b4c8422ebe08@web.de","threadId":"61700","inReplyTo":"eh556xjlxipeyumm3gchiiggyogpcuznil5kwvudcpyecj4uxq@uy5tk66blzsl","subject":"Re: [PATCH 3/6] t-ctype: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-01T20:04:52Z","receivedAt":"2024-07-01T20:05:00Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 01.07.24 um 21:49 schrieb Josh Steadmon:\n> On 2024.06.29 17:44, René Scharfe wrote:\n>> Use the macro TEST_RUN instead of the internal functions\n>> test__run_begin() and test__run_end().\n>>\n>> Signed-off-by: René Scharfe <l.s.r@web.de>\n>\n> Nitpick: please expand the commit message here as you did for the\n> following patches.\n\nThere is not much more to it: Use of internal functions is discouraged,\nso this patch replaces their calls with the new public feature.  Perhaps\na \"Kinda-requested-by: Phillip Wood <phillip.wood@dunelm.org.uk>\" would\nbe appropriate?\n\nUnlike in the later patches the tests themselves are unchanged, so this\nhas no consequence for people that want to add or modify character\nclassifier tests.\n\nRené\n"},{"id":"497929","messageId":"6bd64e76-46b1-4511-a8f7-bfe28af48fb3@web.de","threadId":"61700","inReplyTo":"bh5ectgtwdaeltflggzwnwhobewvcqqelforr4sfcmq47rnnrl@mzspptlhpofx","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-01T20:18:45Z","receivedAt":"2024-07-01T20:18:59Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 01.07.24 um 21:58 schrieb Josh Steadmon:\n> On 2024.06.29 17:47, René Scharfe wrote:\n>> The macro TEST takes a single expression.  If a test requires multiple\n>> statements then they need to be placed in a function that's called in\n>> the TEST expression.  The functions setup() and setup_populated() here\n>> are used for that purpose and take another function as an argument,\n>> making the control flow hard to follow.\n>>\n>> Remove the overhead of these functions by using TEST_RUN instead.  Move\n>> their duplicate post-condition checks into a new helper, t_release(),\n>> and let t_addch() and t_addstr() accept properly typed input parameters\n>> instead of void pointers.\n>>\n>> Use the fully checking t_addstr() for adding initial values instead of\n>> only doing only a length comparison -- there's no need for skipping the\n>> other checks.\n>>\n>> This results in test cases that look much more like strbuf usage in\n>> production code, only with checked strbuf functions replaced by checking\n>> wrappers.\n>>\n>> Signed-off-by: René Scharfe <l.s.r@web.de>\n>> ---\n>>  t/unit-tests/t-strbuf.c | 79 +++++++++++++++++++++--------------------\n>>  1 file changed, 41 insertions(+), 38 deletions(-)\n>>\n>> diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\n>> index 6027dafef7..c8e39ddda7 100644\n>> --- a/t/unit-tests/t-strbuf.c\n>> +++ b/t/unit-tests/t-strbuf.c\n>> @@ -1,32 +1,6 @@\n>>  #include \"test-lib.h\"\n>>  #include \"strbuf.h\"\n>>\n>> -/* wrapper that supplies tests with an empty, initialized strbuf */\n>> -static void setup(void (*f)(struct strbuf*, const void*),\n>> -\t\t  const void *data)\n>> -{\n>> -\tstruct strbuf buf = STRBUF_INIT;\n>> -\n>> -\tf(&buf, data);\n>> -\tstrbuf_release(&buf);\n>> -\tcheck_uint(buf.len, ==, 0);\n>> -\tcheck_uint(buf.alloc, ==, 0);\n>> -}\n>> -\n>> -/* wrapper that supplies tests with a populated, initialized strbuf */\n>> -static void setup_populated(void (*f)(struct strbuf*, const void*),\n>> -\t\t\t    const char *init_str, const void *data)\n>> -{\n>> -\tstruct strbuf buf = STRBUF_INIT;\n>> -\n>> -\tstrbuf_addstr(&buf, init_str);\n>> -\tcheck_uint(buf.len, ==, strlen(init_str));\n>> -\tf(&buf, data);\n>> -\tstrbuf_release(&buf);\n>> -\tcheck_uint(buf.len, ==, 0);\n>> -\tcheck_uint(buf.alloc, ==, 0);\n>> -}\n>> -\n>>  static int assert_sane_strbuf(struct strbuf *buf)\n>>  {\n>>  \t/* Initialized strbufs should always have a non-NULL buffer */\n>> @@ -66,10 +40,8 @@ static void t_dynamic_init(void)\n>>  \tstrbuf_release(&buf);\n>>  }\n>>\n>> -static void t_addch(struct strbuf *buf, const void *data)\n>> +static void t_addch(struct strbuf *buf, int ch)\n>>  {\n>> -\tconst char *p_ch = data;\n>> -\tconst char ch = *p_ch;\n>>  \tsize_t orig_alloc = buf->alloc;\n>>  \tsize_t orig_len = buf->len;\n>>\n>> @@ -85,9 +57,8 @@ static void t_addch(struct strbuf *buf, const void *data)\n>>  \tcheck_char(buf->buf[buf->len], ==, '\\0');\n>>  }\n>>\n>> -static void t_addstr(struct strbuf *buf, const void *data)\n>> +static void t_addstr(struct strbuf *buf, const char *text)\n>>  {\n>> -\tconst char *text = data;\n>>  \tsize_t len = strlen(text);\n>>  \tsize_t orig_alloc = buf->alloc;\n>>  \tsize_t orig_len = buf->len;\n>> @@ -105,18 +76,50 @@ static void t_addstr(struct strbuf *buf, const void *data)\n>>  \tcheck_str(buf->buf + orig_len, text);\n>>  }\n>>\n>> +static void t_release(struct strbuf *sb)\n>> +{\n>> +\tstrbuf_release(sb);\n>> +\tcheck_uint(sb->len, ==, 0);\n>> +\tcheck_uint(sb->alloc, ==, 0);\n>> +}\n>> +\n>>  int cmd_main(int argc, const char **argv)\n>>  {\n>>  \tif (!TEST(t_static_init(), \"static initialization works\"))\n>>  \t\ttest_skip_all(\"STRBUF_INIT is broken\");\n>>  \tTEST(t_dynamic_init(), \"dynamic initialization works\");\n>\n> IIUC you're leaving t_static_init() as-is so that we can determine\n> whether or not to skip the rest of the tests,\n\nRight.  And that place might be a use case for an official version of\ntest__run_end(), but it's probably better to gather a few more such\ncandidates before drawing conclusions.\n\n> but is there a reason you\n> didn't convert t_dynamic_init() here?\n\nGood question, I don't remember.  Perhaps an oversight or laziness.\nOr just a general feeling to leave the init tests alone since the\nfirst one doesn't map to TEST_RUN easily.  So, in a word: No.\n\n>> -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n>> -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n>> -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n>> -\t     \"strbuf_addch appends to initial value\");\n>> -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n>> -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n>> -\t     \"strbuf_addstr appends string to initial value\");\n>> +\n>> +\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>> +\t\tt_addch(&sb, 'a');\n>> +\t\tt_release(&sb);\n>> +\t}\n>> +\n>> +\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>> +\t\tt_addch(&sb, '\\0');\n>> +\t\tt_release(&sb);\n>> +\t}\n>> +\n>> +\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>> +\t\tt_addstr(&sb, \"initial value\");\n>> +\t\tt_addch(&sb, 'a');\n>> +\t\tt_release(&sb);\n>> +\t}\n>> +\n>> +\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>> +\t\tt_addstr(&sb, \"hello there\");\n>> +\t\tt_release(&sb);\n>> +\t}\n>> +\n>> +\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>> +\t\tt_addstr(&sb, \"initial value\");\n>> +\t\tt_addstr(&sb, \"hello there\");\n>> +\t\tt_release(&sb);\n>> +\t}\n>>\n>>  \treturn test_done();\n>>  }\n>> --\n>> 2.45.2\n>\n> I think this commit in particular shows how TEST_RUN() is more\n> convenient than TEST(). (Although, arguably we shouldn't have allowed\n> the setup() + callback situation to start with.)\n\nGreat, thanks!\n\nRené\n"},{"id":"497940","messageId":"20240701221019.GA20683@coredump.intra.peff.net","threadId":"61700","inReplyTo":"xmqqed8cx6q0.fsf@gitster.g","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-07-01T22:10:19Z","receivedAt":"2024-07-01T22:10:20Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 01, 2024 at 12:17:11PM -0700, Junio C Hamano wrote:\n\n> > But I do have such an \"EOT\" patch which I've been meaning to send out,\n> > since it makes many of these quoting annoyances go away (though of\n> > course it leaves the indentation).\n> \n> I am not sure about your \"test body comes from the standard input\"\n> (not saying \"I am not convinced it is a good idea\" or even \"I am\n> convinced it is a bad idea\"---I do not know what to think about it,\n> not just yet).  THe above illustration does make it easier to grok\n> by keeping everything in one place.\n\nI just (re-)posted it in:\n\n  https://lore.kernel.org/git/20240701220815.GA20293@coredump.intra.peff.net/\n\nso you can see the improvement in some other real cases.\n\n-Peff\n"},{"id":"497941","messageId":"20240701221815.GB20683@coredump.intra.peff.net","threadId":"61700","inReplyTo":"177e98da-803e-4839-9484-fd24828bd21b@web.de","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-07-01T22:18:15Z","receivedAt":"2024-07-01T22:18:16Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 01, 2024 at 09:51:31PM +0200, René Scharfe wrote:\n\n> > would that be preferable to moving it to its own file? I kind of like\n> > keeping everything in the test scripts themselves so related changes can\n> > happen side-by-side, though I admit in this case it is intimately tied\n> > to the separate test-example-tap.c source anyway.\n> \n> I can't think of an example where we keep test definitions in the same\n> file as the code to be tested.  It would be somewhat cool to empower the\n> unit test framework to test itself, but I suspect that this nesting\n> ability would be hard to achieve and not very useful otherwise.  And\n> would we be able to trust such a self-test?\n\nYeah, I think this is really a special case, just because we're not\ntesting specific items, but rather sanity-checking the TAP output\nitself.\n\nI think even in the unit tests, we're mostly carrying expected output\nnext to the code (and the unit test harness is checking those and\nproducing appropriate messages). It's only this \"run a bunch of tests\nthat might themselves fail and see if the output was sensible\" that it's\nhard to do this for. I.e., t0080 is special and weird.\n\n> The only downside of keeping the expected output of t0080 separate that\n> I can think of is that it might get confusing if we'd ever add more\n> test_expect_success calls to it, but I can't imagine why we'd want to\n> do that.\n\nYeah, I think it is mostly a lost cause here. We are far away from the\nsource code whether it is a here-doc or a separate file.\n\nI guess I was responding more to the principle that external files are\nusually just distracting and annoying (another annoyance: they inhibit\ntab completion ;) ). But it is not that big a deal either way in this\ncase.\n\n> Being able to pass the test code to test_expect_success as a here-doc or\n> file to avoid nested shell quoting sounds useful in general.  For t0080\n> we could achieve the same effect already by creating the expect file\n> before calling test_expect_success.  That has the downside of passing\n> even when the disk is full and the files are created empty, but we can\n> throw in a \"test -s\" to rule it out.\n\nYup, agreed with everything there.\n\n-Peff\n"},{"id":"497946","messageId":"xmqqcynwu1h6.fsf@gitster.g","threadId":"61700","inReplyTo":"20240701221019.GA20683@coredump.intra.peff.net","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-01T23:38:45Z","receivedAt":"2024-07-01T23:38:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Mon, Jul 01, 2024 at 12:17:11PM -0700, Junio C Hamano wrote:\n>\n>> > But I do have such an \"EOT\" patch which I've been meaning to send out,\n>> > since it makes many of these quoting annoyances go away (though of\n>> > course it leaves the indentation).\n>> \n>> I am not sure about your \"test body comes from the standard input\"\n>> (not saying \"I am not convinced it is a good idea\" or even \"I am\n>> convinced it is a bad idea\"---I do not know what to think about it,\n>> not just yet).  THe above illustration does make it easier to grok\n>> by keeping everything in one place.\n>\n> I just (re-)posted it in:\n>\n>   https://lore.kernel.org/git/20240701220815.GA20293@coredump.intra.peff.net/\n>\n> so you can see the improvement in some other real cases.\n\n;-)\n\nThe shells we care about (and that does not include the /bin/sh on\nancient Solaris ☹) should be OK, but \"IFS= read -r line\" somehow\nmakes me feel nervous.  Maybe I am superstitious.\n\nBoth steps look quite good.  Will queue.\n\nThanks.\n"},{"id":"497951","messageId":"20240702005743.GB27170@coredump.intra.peff.net","threadId":"61700","inReplyTo":"xmqqcynwu1h6.fsf@gitster.g","subject":"Re: [PATCH 1/6] t0080: move expected output to a file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-07-02T00:57:43Z","receivedAt":"2024-07-02T00:57:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 01, 2024 at 04:38:45PM -0700, Junio C Hamano wrote:\n\n> > I just (re-)posted it in:\n> >\n> >   https://lore.kernel.org/git/20240701220815.GA20293@coredump.intra.peff.net/\n> >\n> > so you can see the improvement in some other real cases.\n> \n> ;-)\n> \n> The shells we care about (and that does not include the /bin/sh on\n> ancient Solaris ☹) should be OK, but \"IFS= read -r line\" somehow\n> makes me feel nervous.  Maybe I am superstitious.\n\nI don't know offhand of any case where it will fail. I'd prefer to start\nwith it and see if it bites us, given that it saves us a process\ninvocation per test (and those do add up in some cases).\n\nI also wondered if we might be able to save the syscall-per-byte\noverhead of \"read\" for some shells (since we know we are reading until\nEOF anyway). Using \"read -N\" with bash would let us do that, but\nobviously we'd still need to fall back to regular \"read\" for other\nshells. I don't think it's worth the complexity unless we can really\nshow a measurable speedup (and I didn't seem to see one).\n\n-Peff\n"},{"id":"497973","messageId":"51ebc0fd-ddae-4bdb-a5d6-c92ce8b1f3e6@gmail.com","threadId":"61700","inReplyTo":"8175f239-8d4e-49f7-ae0d-dba7df8c365d@web.de","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-02T15:13:58Z","receivedAt":"2024-07-02T15:14:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 29/06/2024 16:43, René Scharfe wrote:\n> The macro TEST only allows defining a test that consists of a single\n> expression.  Add the new macro, TEST_RUN, which provides a way to define\n> unit tests that are made up of one or more statements.  A test started\n> with it implicitly ends when the next test is started or test_done() is\n> called.\n> \n> TEST_RUN allows defining self-contained tests en bloc, a bit like\n> test_expect_success does for regular tests.  Unlike TEST it does not\n> require defining wrapper functions for test statements.\n\nThere are pros and cons to not requiring one function per test. It can \nbe a pain to have to write separate functions but it keeps each test \nself contained which hopefully makes it harder to have accidental \ndependencies between tests. Having separate functions for each test \nmakes it easy to initialize and free resources for every test by writing \na setup() function that initializes the resources, calls the test \nfunction and then frees the resources. The changes in patch 6 to use \nTEST_RUN() mean that each test now has more boilerplate to initialize \nand free the strbuf. Having each test in its own function also makes \nmain() shorter and which means can quickly get an overview of all the \ntest cases from it.\n\nOn the other hand having all the tests defined in main() using \nTEST_RUN() means we can just write the test body without having to \ndefine a separate function and then call it with TEST()\n\n> No public method is provided for ending a test explicitly, yet; let's\n> see if we'll ever need one.\n\nThis means that we do not error out if there are accidentally nested \ntests. That probably does not matter too much.\n\n\n> +int test__run(const char *location, const char *format, ...)\n> +{\n> +\tva_list ap;\n> +\tchar *desc;\n> +\n> +\ttest__run_maybe_end();\n> +\n> +\tva_start(ap, format);\n> +\tdesc = xstrvfmt(format, ap);\n\nThis uses an strbuf under the hood. So far we've avoided doing that as \nwe want to be able to test the strbuf implementation with this test \nframework. We don't need to support arbitrary length strings here so we \ncould use a fixed array and xsnprinf() instead.\n\n> +/*\n> + * Start a test, returns 1 if the test was actually started or 0 if it\n> + * was skipped.  The test ends when the next test starts or test_done()\n> + * is called.\n> + */\n> +#define TEST_RUN(...) test__run(TEST_LOCATION(), __VA_ARGS__)\n\nLooking ahead the plan seems to be to change most instances of TEST() to \nTEST_RUN(). If we are going to go that way perhaps we should steal \nTEST() for this macro and rename the existing TEST() macro.\n\nI'm not very enthusiastic about requiring the test author to wrap \nTEST_RUN() in an if() statement rather than just doing that for them. It \nmakes it explicit but from the test author's point of view it just feels \nlike pointless boilerplate.\n\n>   /*\n>    * test_done() must be called at the end of main(). It will print the\n>    * plan if plan() was not called at the beginning of the test program\n> @@ -156,6 +163,7 @@ extern union test__tmp test__tmp[2];\n>   int test__run_begin(void);\n>   __attribute__((format (printf, 3, 4)))\n>   int test__run_end(int, const char *, const char *, ...);\n\nWe should add\n\n     __attribute__((format (printf, 2, 3), warn_unused_result))\n\nhere to catch any errors in the format string / arguments and to warn if \nTEST_RUN() isn't wrapped in an if() statement.\n\nBest Wishes\n\nPhillip\n\n> +int test__run(const char *location, const char *format, ...);\n>   void test__todo_begin(void);\n>   int test__todo_end(const char *, const char *, int);\n> \n> --\n> 2.45.2\n"},{"id":"497974","messageId":"eae0ccf4-8747-400c-96ab-b7fc7a51beb9@gmail.com","threadId":"61700","inReplyTo":"1a9c7351-68c2-4f2a-b00f-b4c8422ebe08@web.de","subject":"Re: [PATCH 3/6] t-ctype: use TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-02T15:14:09Z","receivedAt":"2024-07-02T15:14:12Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 01/07/2024 21:04, René Scharfe wrote:\n> Am 01.07.24 um 21:49 schrieb Josh Steadmon:\n>> On 2024.06.29 17:44, René Scharfe wrote:\n>>> Use the macro TEST_RUN instead of the internal functions\n>>> test__run_begin() and test__run_end().\n>>>\n>>> Signed-off-by: René Scharfe <l.s.r@web.de>\n>>\n>> Nitpick: please expand the commit message here as you did for the\n>> following patches.\n> \n> There is not much more to it: Use of internal functions is discouraged,\n> so this patch replaces their calls with the new public feature.  Perhaps\n> a \"Kinda-requested-by: Phillip Wood <phillip.wood@dunelm.org.uk>\" would\n> be appropriate?\n\nPerhaps something like\n\nThese tests use the internal functions test__run_begin() and \ntest__run_end() which are supposed to be private to the test framework. \nConvert them to use the newly added TEST_RUN() instead.\n\nwould be sufficient?\n\nBest Wishes\n\nPhillip\n\n> Unlike in the later patches the tests themselves are unchanged, so this\n> has no consequence for people that want to add or modify character\n> classifier tests.\n> \n> René\n"},{"id":"497975","messageId":"c122c0a6-9f38-489d-8286-b62bd4deefcc@gmail.com","threadId":"61700","inReplyTo":"27cdab5f-15b3-45fb-89e1-530a6c0817a6@web.de","subject":"Re: [PATCH 5/6] t-strvec: use TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-02T15:14:28Z","receivedAt":"2024-07-02T15:14:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 29/06/2024 16:46, René Scharfe wrote:\n> The macro TEST takes a single expression.  If a test requires multiple\n> statements then they need to be placed in a function that's called in\n> the TEST expression.\n> \n> Remove the overhead of defining and calling single-use functions by\n> using TEST_RUN instead.\n\nI'm not sure what the overhead is that you keep referring to - the \ncompiler will inline functions that are only called once so I presume \nyou mean overhead for test authors or contributors reading the code?\n\nThis is not related to you changes but while looking at this patch I \nnoticed that the existing code in check_strvec_loc() contains\n\n\t\tif (!check_uint(vec->nr, >, nr) ||\n\t\t    !check_uint(vec->alloc, >, nr) ||\n\t\t    !check_str(vec->v[nr], str)) {\n\t\t\tstruct strbuf msg = STRBUF_INIT;\n\t\t\tstrbuf_addf(&msg, \"strvec index %\"PRIuMAX, (uintmax_t) nr);\n\t\t\ttest_assert(loc, msg.buf, 0);\n\t\t\tstrbuf_release(&msg);\n\t\t\tva_end(ap);\n\t\t\treturn;\n\nWhich looks like it should be using test_msg() instead of writing a \nmessage to an strbuf and calling test_assert().\n\nThe conversion itself looks correct. It is a shame that each test has to \nhave boilerplate to initalize and free the strvec but that comes from \nthe existing implementation.\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>   t/unit-tests/t-strvec.c | 356 ++++++++++++++++++----------------------\n>   1 file changed, 156 insertions(+), 200 deletions(-)\n> \n> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c\n> index d4615ab06d..00ff7d4ae8 100644\n> --- a/t/unit-tests/t-strvec.c\n> +++ b/t/unit-tests/t-strvec.c\n> @@ -36,237 +36,193 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n>   \tcheck_pointer_eq(vec->v[nr], NULL);\n>   }\n> \n> -static void t_static_init(void)\n> +int cmd_main(int argc, const char **argv)\n>   {\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tcheck_pointer_eq(vec.v, empty_strvec);\n> -\tcheck_uint(vec.nr, ==, 0);\n> -\tcheck_uint(vec.alloc, ==, 0);\n> -}\n> +\tif (TEST_RUN(\"static initialization\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n> +\t\tcheck_uint(vec.nr, ==, 0);\n> +\t\tcheck_uint(vec.alloc, ==, 0);\n> +\t}\n> \n> -static void t_dynamic_init(void)\n> -{\n> -\tstruct strvec vec;\n> -\tstrvec_init(&vec);\n> -\tcheck_pointer_eq(vec.v, empty_strvec);\n> -\tcheck_uint(vec.nr, ==, 0);\n> -\tcheck_uint(vec.alloc, ==, 0);\n> -}\n> +\tif (TEST_RUN(\"dynamic initialization\")) {\n> +\t\tstruct strvec vec;\n> +\t\tstrvec_init(&vec);\n> +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n> +\t\tcheck_uint(vec.nr, ==, 0);\n> +\t\tcheck_uint(vec.alloc, ==, 0);\n> +\t}\n> \n> -static void t_clear(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_push(&vec, \"foo\");\n> -\tstrvec_clear(&vec);\n> -\tcheck_pointer_eq(vec.v, empty_strvec);\n> -\tcheck_uint(vec.nr, ==, 0);\n> -\tcheck_uint(vec.alloc, ==, 0);\n> -}\n> +\tif (TEST_RUN(\"clear\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_push(&vec, \"foo\");\n> +\t\tstrvec_clear(&vec);\n> +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n> +\t\tcheck_uint(vec.nr, ==, 0);\n> +\t\tcheck_uint(vec.alloc, ==, 0);\n> +\t}\n> \n> -static void t_push(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> +\tif (TEST_RUN(\"push\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> \n> -\tstrvec_push(&vec, \"foo\");\n> -\tcheck_strvec(&vec, \"foo\", NULL);\n> +\t\tstrvec_push(&vec, \"foo\");\n> +\t\tcheck_strvec(&vec, \"foo\", NULL);\n> \n> -\tstrvec_push(&vec, \"bar\");\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> +\t\tstrvec_push(&vec, \"bar\");\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> \n> -\tstrvec_clear(&vec);\n> -}\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_pushf(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushf(&vec, \"foo: %d\", 1);\n> -\tcheck_strvec(&vec, \"foo: 1\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"pushf\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n> +\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_pushl(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"pushl\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_pushv(void)\n> -{\n> -\tconst char *strings[] = {\n> -\t\t\"foo\", \"bar\", \"baz\", NULL,\n> -\t};\n> -\tstruct strvec vec = STRVEC_INIT;\n> +\tif (TEST_RUN(\"pushv\")) {\n> +\t\tconst char *strings[] = {\n> +\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n> +\t\t};\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> \n> -\tstrvec_pushv(&vec, strings);\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_pushv(&vec, strings);\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> \n> -\tstrvec_clear(&vec);\n> -}\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_replace_at_head(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_replace(&vec, 0, \"replaced\");\n> -\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"replace at head\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_replace(&vec, 0, \"replaced\");\n> +\t\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_replace_at_tail(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_replace(&vec, 2, \"replaced\");\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"replace at tail\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_replace(&vec, 2, \"replaced\");\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_replace_in_between(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_replace(&vec, 1, \"replaced\");\n> -\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"replace in between\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_replace(&vec, 1, \"replaced\");\n> +\t\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_replace_with_substring(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", NULL);\n> -\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n> -\tcheck_strvec(&vec, \"oo\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"replace with substring\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", NULL);\n> +\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n> +\t\tcheck_strvec(&vec, \"oo\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_remove_at_head(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_remove(&vec, 0);\n> -\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"remove at head\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_remove(&vec, 0);\n> +\t\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_remove_at_tail(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_remove(&vec, 2);\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"remove at tail\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_remove(&vec, 2);\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_remove_in_between(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_remove(&vec, 1);\n> -\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"remove in between\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_remove(&vec, 1);\n> +\t\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_pop_empty_array(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pop(&vec);\n> -\tcheck_strvec(&vec, NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"pop with empty array\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pop(&vec);\n> +\t\tcheck_strvec(&vec, NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_pop_non_empty_array(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_pop(&vec);\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"pop with non-empty array\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_pop(&vec);\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_split_empty_string(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_split(&vec, \"\");\n> -\tcheck_strvec(&vec, NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"split empty string\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_split(&vec, \"\");\n> +\t\tcheck_strvec(&vec, NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_split_single_item(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_split(&vec, \"foo\");\n> -\tcheck_strvec(&vec, \"foo\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"split single item\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_split(&vec, \"foo\");\n> +\t\tcheck_strvec(&vec, \"foo\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_split_multiple_items(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_split(&vec, \"foo bar baz\");\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"split multiple items\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_split(&vec, \"foo bar baz\");\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_split_whitespace_only(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_split(&vec, \" \\t\\n\");\n> -\tcheck_strvec(&vec, NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"split whitespace only\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_split(&vec, \" \\t\\n\");\n> +\t\tcheck_strvec(&vec, NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_split_multiple_consecutive_whitespaces(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tstrvec_split(&vec, \"foo\\n\\t bar\");\n> -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> -\tstrvec_clear(&vec);\n> -}\n> +\tif (TEST_RUN(\"split multiple consecutive whitespaces\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n> +\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n> +\t\tstrvec_clear(&vec);\n> +\t}\n> \n> -static void t_detach(void)\n> -{\n> -\tstruct strvec vec = STRVEC_INIT;\n> -\tconst char **detached;\n> +\tif (TEST_RUN(\"detach\")) {\n> +\t\tstruct strvec vec = STRVEC_INIT;\n> +\t\tconst char **detached;\n> \n> -\tstrvec_push(&vec, \"foo\");\n> +\t\tstrvec_push(&vec, \"foo\");\n> \n> -\tdetached = strvec_detach(&vec);\n> -\tcheck_str(detached[0], \"foo\");\n> -\tcheck_pointer_eq(detached[1], NULL);\n> +\t\tdetached = strvec_detach(&vec);\n> +\t\tcheck_str(detached[0], \"foo\");\n> +\t\tcheck_pointer_eq(detached[1], NULL);\n> \n> -\tcheck_pointer_eq(vec.v, empty_strvec);\n> -\tcheck_uint(vec.nr, ==, 0);\n> -\tcheck_uint(vec.alloc, ==, 0);\n> +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n> +\t\tcheck_uint(vec.nr, ==, 0);\n> +\t\tcheck_uint(vec.alloc, ==, 0);\n> \n> -\tfree((char *) detached[0]);\n> -\tfree(detached);\n> -}\n> +\t\tfree((char *) detached[0]);\n> +\t\tfree(detached);\n> +\t}\n> \n> -int cmd_main(int argc, const char **argv)\n> -{\n> -\tTEST(t_static_init(), \"static initialization\");\n> -\tTEST(t_dynamic_init(), \"dynamic initialization\");\n> -\tTEST(t_clear(), \"clear\");\n> -\tTEST(t_push(), \"push\");\n> -\tTEST(t_pushf(), \"pushf\");\n> -\tTEST(t_pushl(), \"pushl\");\n> -\tTEST(t_pushv(), \"pushv\");\n> -\tTEST(t_replace_at_head(), \"replace at head\");\n> -\tTEST(t_replace_in_between(), \"replace in between\");\n> -\tTEST(t_replace_at_tail(), \"replace at tail\");\n> -\tTEST(t_replace_with_substring(), \"replace with substring\");\n> -\tTEST(t_remove_at_head(), \"remove at head\");\n> -\tTEST(t_remove_in_between(), \"remove in between\");\n> -\tTEST(t_remove_at_tail(), \"remove at tail\");\n> -\tTEST(t_pop_empty_array(), \"pop with empty array\");\n> -\tTEST(t_pop_non_empty_array(), \"pop with non-empty array\");\n> -\tTEST(t_split_empty_string(), \"split empty string\");\n> -\tTEST(t_split_single_item(), \"split single item\");\n> -\tTEST(t_split_multiple_items(), \"split multiple items\");\n> -\tTEST(t_split_whitespace_only(), \"split whitespace only\");\n> -\tTEST(t_split_multiple_consecutive_whitespaces(), \"split multiple consecutive whitespaces\");\n> -\tTEST(t_detach(), \"detach\");\n>   \treturn test_done();\n>   }\n> --\n> 2.45.2\n"},{"id":"497977","messageId":"d768f700-654a-4030-bd45-e8e3dea351b5@gmail.com","threadId":"61700","inReplyTo":"bh5ectgtwdaeltflggzwnwhobewvcqqelforr4sfcmq47rnnrl@mzspptlhpofx","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-02T15:14:37Z","receivedAt":"2024-07-02T15:14:39Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Josh and René\n\nOn 01/07/2024 20:58, Josh Steadmon wrote:\n> On 2024.06.29 17:47, René Scharfe wrote:\n>\n> I think this commit in particular shows how TEST_RUN() is more\n> convenient than TEST(). (Although, arguably we shouldn't have allowed\n> the setup() + callback situation to start with.)\n\nI think the counterargument to that is that using TEST_RUN() makes the \ntests noisier and more error prone because each one has to be wrapped in \nan if() statement and has more boiler plate initializing and freeing the \nstrbuf rather than getting that for free by calling the test function \nvia setup().\n\nHaving said that I don't mind the changes in this patch if that's the \nway others want to go. Getting rid of the untyped test arguments is \ndefinitely a benefit of this approach.\n\nBest Wishes\n\nPhillip\n\n\n"},{"id":"497981","messageId":"xmqqle2jssfs.fsf@gitster.g","threadId":"61700","inReplyTo":"51ebc0fd-ddae-4bdb-a5d6-c92ce8b1f3e6@gmail.com","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-02T15:51:35Z","receivedAt":"2024-07-02T15:51:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"phillip.wood123@gmail.com writes:\n\n>> No public method is provided for ending a test explicitly, yet; let's\n>> see if we'll ever need one.\n>\n> This means that we do not error out if there are accidentally nested\n> tests. That probably does not matter too much.\n\nIsn't it more like it is impossible to create nested tests, since a\n\"begin\" implicitly ends the current one before starting the new one?\n\n>> @@ -156,6 +163,7 @@ extern union test__tmp test__tmp[2];\n>>   int test__run_begin(void);\n>>   __attribute__((format (printf, 3, 4)))\n>>   int test__run_end(int, const char *, const char *, ...);\n>\n> We should add\n>\n>     __attribute__((format (printf, 2, 3), warn_unused_result))\n>\n> here to catch any errors in the format string / arguments and to warn\n> if TEST_RUN() isn't wrapped in an if() statement.\n\nNice.  Especially the \"unused\" check is valuable.\n\nThanks.\n\n"},{"id":"497983","messageId":"D2F8BS39IFC4.WJ9TNK3IDFBV@gmail.com","threadId":"61700","inReplyTo":"bh5ectgtwdaeltflggzwnwhobewvcqqelforr4sfcmq47rnnrl@mzspptlhpofx","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Ghanshyam Thakkar","fromEmail":"shyamthakkar001@gmail.com","sentAt":"2024-07-02T17:29:18Z","receivedAt":"2024-07-02T17:29:23Z","isPatch":true,"sender":{"key":"shyamthakkar001@gmail.com","avatar":"https://avatars.githubusercontent.com/u/72698233?v=4"},"body":"Josh Steadmon <steadmon@google.com> wrote:\n> > -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n> > -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n> > -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> > -\t     \"strbuf_addch appends to initial value\");\n> > -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n> > -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n> > -\t     \"strbuf_addstr appends string to initial value\");\n> > +\n> > +\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n> > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > +\t\tt_addch(&sb, 'a');\n> > +\t\tt_release(&sb);\n> > +\t}\n> > +\n> > +\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n> > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > +\t\tt_addch(&sb, '\\0');\n> > +\t\tt_release(&sb);\n> > +\t}\n> > +\n> > +\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n> > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > +\t\tt_addstr(&sb, \"initial value\");\n> > +\t\tt_addch(&sb, 'a');\n> > +\t\tt_release(&sb);\n> > +\t}\n> > +\n> > +\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n> > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > +\t\tt_addstr(&sb, \"hello there\");\n> > +\t\tt_release(&sb);\n> > +\t}\n> > +\n> > +\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n> > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > +\t\tt_addstr(&sb, \"initial value\");\n> > +\t\tt_addstr(&sb, \"hello there\");\n> > +\t\tt_release(&sb);\n> > +\t}\n> > \n> >  \treturn test_done();\n> >  }\n> > --\n> > 2.45.2\n>\n> I think this commit in particular shows how TEST_RUN() is more\n> convenient than TEST(). (Although, arguably we shouldn't have allowed\n> the setup() + callback situation to start with.)\n\nCould you expand a bit on why the setup() + callback thing shouldn't be\nallowed? I think it is a nice way of avoiding boilerplate and having\nindependent state. And, I see the true potential of TEST_RUN() in\ntestcases defined through macros rather than replacing functions. I\nactually think that the previous version with the functions was not\nparticularly bad, and I agree with Phillip that the previous version's\nmain() provided nice overview of the tests and it was easier to\nverify the independence between each testcase.\n\nPerhaps, the code snippets inside the functions are small enough to\nperceive TEST_RUN() as more convenient than TEST() in this test, but,\nfor future reference, I definitely don't think TEST_RUN() should be\nlooked at as a replacement for TEST(), and more like 'when we have to\nuse macro magic which requires us to use internal test__run_*\nfunctions, using TEST_RUN() is more convenient'. Patch [3/6] is a good\nexample of that. But, I also don't mind if patches 4, 5, or 6 get\nmerged as I don't see any difference between using TEST_RUN() or\nTEST() in those patches, besides moving everything inside main().\n\nThanks.\n"},{"id":"497992","messageId":"fc83c8da-4808-4e52-bfda-6b2d0c5cb553@web.de","threadId":"61700","inReplyTo":"c122c0a6-9f38-489d-8286-b62bd4deefcc@gmail.com","subject":"Re: [PATCH 5/6] t-strvec: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-02T20:55:17Z","receivedAt":"2024-07-02T20:55:25Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.07.24 um 17:14 schrieb phillip.wood123@gmail.com:\n> Hi René\n>\n> On 29/06/2024 16:46, René Scharfe wrote:\n>> The macro TEST takes a single expression.  If a test requires\n>> multiple statements then they need to be placed in a function\n>> that's called in the TEST expression.\n>>\n>> Remove the overhead of defining and calling single-use functions\n>> by using TEST_RUN instead.\n>\n> I'm not sure what the overhead is that you keep referring to - the\n> compiler will inline functions that are only called once so I presume\n> you mean overhead for test authors or contributors reading the code?\n\nYes, I mean the additional effort for developers to come up with\nfunction names and write single-call functions, and for readers to\nmentally connect test description and function.  Of course you still can\ndo that with TEST_RUN, but unlike TEST it doesn't force you.\n\nI don't care much about changed duration of compilation or execution\nhere -- it shouldn't be much either way.\n\n> This is not related to you changes but while looking at this patch I\n> noticed that the existing code in check_strvec_loc() contains\n>\n>\n>         if (!check_uint(vec->nr, >, nr) ||\n>             !check_uint(vec->alloc, >, nr) ||\n>             !check_str(vec->v[nr], str)) {\n>             struct strbuf msg = STRBUF_INIT;\n>             strbuf_addf(&msg, \"strvec index %\"PRIuMAX, (uintmax_t) nr);\n>             test_assert(loc, msg.buf, 0);\n>             strbuf_release(&msg);\n>             va_end(ap);\n>             return;\n>\n> Which looks like it should be using test_msg() instead of writing a\n> message to an strbuf and calling test_assert().\n\nErm, yes, good find.  You know something isn't right if you see an\nassert(0) or similar..\n\n> The conversion itself looks correct. It is a shame that each test has\n> to have boilerplate to initalize and free the strvec but that comes\n> from the existing implementation.\n\nI see that as a benefit: You can see what each test does at a glance;\nno tricks, no dependencies.  Just exercise steps and checks.\n\nRené\n"},{"id":"497993","messageId":"e4da5c4d-2900-4d62-b855-7c4382a2b949@web.de","threadId":"61700","inReplyTo":"d768f700-654a-4030-bd45-e8e3dea351b5@gmail.com","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-02T20:55:19Z","receivedAt":"2024-07-02T20:55:27Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.07.24 um 17:14 schrieb phillip.wood123@gmail.com:\n> Hi Josh and René\n>\n> On 01/07/2024 20:58, Josh Steadmon wrote:\n>> On 2024.06.29 17:47, René Scharfe wrote:\n>>\n>> I think this commit in particular shows how TEST_RUN() is more\n>> convenient than TEST(). (Although, arguably we shouldn't have allowed\n>> the setup() + callback situation to start with.)\n>\n> I think the counterargument to that is that using TEST_RUN() makes\n> the tests noisier and more error prone because each one has to be\n> wrapped in an if() statement and has more boiler plate initializing\n> and freeing the strbuf rather than getting that for free by calling\n> the test function via setup().\n\nI guess these are two sides of the same coin.  Explicit initialization\nand cleanup is closer to what real strbuf users do, more idiomatic.\nWhich helps to see what the tests are actually doing.\n\nThe wrapping if is less annoying than a wrapping function that I have\nto name and call.  The condition is just !ctx.skip_all, though, (for\nnow at least) so we could do something like:\n\n   #define TEST_START(...) if (!TEST_RUN(__VA_ARGS__)) return test_done()\n\n... and tests could look like that then:\n\n   TEST_START(\"this and that\");\n   check(this);\n   check(that);\n\n   TEST_START(\"whatever\");\n   check(whatever);\n\nPerhaps a bit too much magic, though?\n\nRené\n"},{"id":"497994","messageId":"332f0418-98eb-4254-b714-cde461f6852d@web.de","threadId":"61700","inReplyTo":"xmqqle2jssfs.fsf@gitster.g","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-02T20:55:11Z","receivedAt":"2024-07-02T20:55:27Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.07.24 um 17:51 schrieb Junio C Hamano:\n> phillip.wood123@gmail.com writes:\n>\n>>> @@ -156,6 +163,7 @@ extern union test__tmp test__tmp[2];\n>>>   int test__run_begin(void);\n>>>   __attribute__((format (printf, 3, 4)))\n>>>   int test__run_end(int, const char *, const char *, ...);\n>>\n>> We should add\n>>\n>>     __attribute__((format (printf, 2, 3), warn_unused_result))\n>>\n>> here to catch any errors in the format string / arguments and to warn\n>> if TEST_RUN() isn't wrapped in an if() statement.\n>\n> Nice.  Especially the \"unused\" check is valuable.\n\nThis confused me for a moment.  I assume you both mean the new function\ntest__run, whose addition was cut from the citation:\n\n   +int test__run(const char *location, const char *format, ...);\n\nFor that one the numbers make sense and the idea is good. :)\n\nRené\n"},{"id":"497995","messageId":"97390954-49bc-48c4-bab1-95be10717aca@web.de","threadId":"61700","inReplyTo":"51ebc0fd-ddae-4bdb-a5d6-c92ce8b1f3e6@gmail.com","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-02T20:55:14Z","receivedAt":"2024-07-02T20:55:28Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.07.24 um 17:13 schrieb phillip.wood123@gmail.com:\n> Hi René\n>\n> On 29/06/2024 16:43, René Scharfe wrote:\n>> The macro TEST only allows defining a test that consists of a\n>> single expression.  Add the new macro, TEST_RUN, which provides a\n>> way to define unit tests that are made up of one or more\n>> statements.  A test started with it implicitly ends when the next\n>> test is started or test_done() is called.\n>>\n>> TEST_RUN allows defining self-contained tests en bloc, a bit like\n>> test_expect_success does for regular tests.  Unlike TEST it does\n>> not require defining wrapper functions for test statements.\n>\n> There are pros and cons to not requiring one function per test. It\n> can be a pain to have to write separate functions but it keeps each\n> test self contained which hopefully makes it harder to have\n> accidental dependencies between tests. Having separate functions for\n> each test makes it easy to initialize and free resources for every\n> test by writing a setup() function that initializes the resources,\n> calls the test function and then frees the resources.\n\nRight.  We should use TEST and TEST_RUN when appropriate.\n\n> The changes in patch 6 to use TEST_RUN() mean that each test now has\n> more boilerplate to initialize and free the strbuf.\nThis makes them more similar to strbuf usage in the wild.  Using\nthe API idiomatically just makes more sense to me.  Not hiding\ninitialization and release makes the tests visibly independent.\nThis is not enforced by TEST_RUN, but made possible by it.\n\n> Having each test in its own function also makes main() shorter and\n> which means can quickly get an overview of all the test cases from\n> it.\n\nThat's true, now you need to grep for TEST_RUN to get such an\noverview.\n\nOn the other hand I find the start of the description in TEST\ninvocations somewhat hard to locate, as they are not vertically\naligned due to the preceding variable-length function name.  Just\nsaying..\n\n>> +int test__run(const char *location, const char *format, ...)\n>> +{\n>> +    va_list ap;\n>> +    char *desc;\n>> +\n>> +    test__run_maybe_end();\n>> +\n>> +    va_start(ap, format);\n>> +    desc = xstrvfmt(format, ap);\n>\n> This uses an strbuf under the hood. So far we've avoided doing that\n> as we want to be able to test the strbuf implementation with this\n> test framework. We don't need to support arbitrary length strings\n> here so we could use a fixed array and xsnprinf() instead.\n\nFair point.  xsnprinf() might be a bit too strict, as it doesn't\nhandle short buffers gracefully.  Perhaps that's OK; a developer\ngetting hit by that could simply increase the buffer size.\n\nWe could also let xstrvfmt() call vsnprintf(3) directly.  The code\nduplication would be a bit grating, but perhaps there's some good\nbase function hidden in there somewhere.\n\n> Looking ahead the plan seems to be to change most instances of TEST()\n> to TEST_RUN(). If we are going to go that way perhaps we should steal\n> TEST() for this macro and rename the existing TEST() macro.\n\nNot my plan, at least -- I'm content with just having the *ability*\nto keep all parts of a test together.\n\nIf we wanted to do that, though, then TEST_RUN would have to be\ncomplemented with a blessed way to check ctx.result in order to handle\nthe t_static_init test of t-strbuf, which I mentioned in my earlier\nreply to Josh.\n\nErr, no, we can simply check the check_* results, like check_strvec_loc\ndoes:\n\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex c8e39ddda7..c765fab53a 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -19,15 +19,6 @@ static int assert_sane_strbuf(struct strbuf *buf)\n \treturn check_uint(buf->len, <, buf->alloc);\n }\n\n-static void t_static_init(void)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-\tcheck_char(buf.buf[0], ==, '\\0');\n-}\n-\n static void t_dynamic_init(void)\n {\n \tstruct strbuf buf;\n@@ -85,8 +76,14 @@ static void t_release(struct strbuf *sb)\n\n int cmd_main(int argc, const char **argv)\n {\n-\tif (!TEST(t_static_init(), \"static initialization works\"))\n-\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n+\tif (TEST_RUN(\"static initialization works\")) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tif (!check_uint(buf.len, ==, 0) ||\n+\t\t    !check_uint(buf.alloc, ==, 0) ||\n+\t\t    !check_char(buf.buf[0], ==, '\\0'))\n+\t\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n+\t}\n+\n \tTEST(t_dynamic_init(), \"dynamic initialization works\");\n\n \tif (TEST_RUN(\"strbuf_addch adds char\")) {\n\n> I'm not very enthusiastic about requiring the test author to wrap\n> TEST_RUN() in an if() statement rather than just doing that for them.\n> It makes it explicit but from the test author's point of view it just\n> feels like pointless boilerplate.\n\nHmm.  We can add more magic, but I suspect that it might confuse\ndevelopers and editors.\n\nRené\n\n\n"},{"id":"497996","messageId":"a0fdf91d-ebbd-4391-8076-f2805e083e1f@web.de","threadId":"61700","inReplyTo":"eae0ccf4-8747-400c-96ab-b7fc7a51beb9@gmail.com","subject":"Re: [PATCH 3/6] t-ctype: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-02T20:55:24Z","receivedAt":"2024-07-02T20:55:31Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.07.24 um 17:14 schrieb phillip.wood123@gmail.com:\n> Hi René\n>\n> On 01/07/2024 21:04, René Scharfe wrote:\n>> Am 01.07.24 um 21:49 schrieb Josh Steadmon:\n>>> On 2024.06.29 17:44, René Scharfe wrote:\n>>>> Use the macro TEST_RUN instead of the internal functions\n>>>> test__run_begin() and test__run_end().\n>>>>\n>>>> Signed-off-by: René Scharfe <l.s.r@web.de>\n>>>\n>>> Nitpick: please expand the commit message here as you did for the\n>>> following patches.\n>>\n>> There is not much more to it: Use of internal functions is discouraged,\n>> so this patch replaces their calls with the new public feature.  Perhaps\n>> a \"Kinda-requested-by: Phillip Wood <phillip.wood@dunelm.org.uk>\" would\n>> be appropriate?\n>\n> Perhaps something like\n>\n> These tests use the internal functions test__run_begin() and\n> test__run_end() which are supposed to be private to the test\n> framework. Convert them to use the newly added TEST_RUN() instead.\n> would be sufficient?\n\nThat works for me.\n\nRené\n"},{"id":"497997","messageId":"c690e0ad-21e1-4b51-b594-70f4b2527df8@web.de","threadId":"61700","inReplyTo":"D2F8BS39IFC4.WJ9TNK3IDFBV@gmail.com","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-02T20:55:21Z","receivedAt":"2024-07-02T20:55:31Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 02.07.24 um 19:29 schrieb Ghanshyam Thakkar:\n> Josh Steadmon <steadmon@google.com> wrote:\n>>> -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n>>> -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n>>> -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n>>> -\t     \"strbuf_addch appends to initial value\");\n>>> -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n>>> -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n>>> -\t     \"strbuf_addstr appends string to initial value\");\n>>> +\n>>> +\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n>>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>>> +\t\tt_addch(&sb, 'a');\n>>> +\t\tt_release(&sb);\n>>> +\t}\n>>> +\n>>> +\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n>>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>>> +\t\tt_addch(&sb, '\\0');\n>>> +\t\tt_release(&sb);\n>>> +\t}\n>>> +\n>>> +\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n>>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>>> +\t\tt_addstr(&sb, \"initial value\");\n>>> +\t\tt_addch(&sb, 'a');\n>>> +\t\tt_release(&sb);\n>>> +\t}\n>>> +\n>>> +\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n>>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>>> +\t\tt_addstr(&sb, \"hello there\");\n>>> +\t\tt_release(&sb);\n>>> +\t}\n>>> +\n>>> +\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n>>> +\t\tstruct strbuf sb = STRBUF_INIT;\n>>> +\t\tt_addstr(&sb, \"initial value\");\n>>> +\t\tt_addstr(&sb, \"hello there\");\n>>> +\t\tt_release(&sb);\n>>> +\t}\n>>>\n>>>  \treturn test_done();\n>>>  }\n>>> --\n>>> 2.45.2\n>>\n>> I think this commit in particular shows how TEST_RUN() is more\n>> convenient than TEST(). (Although, arguably we shouldn't have allowed\n>> the setup() + callback situation to start with.)\n>\n> Could you expand a bit on why the setup() + callback thing shouldn't be\n> allowed? I think it is a nice way of avoiding boilerplate and having\n> independent state. And, I see the true potential of TEST_RUN() in\n> testcases defined through macros rather than replacing functions. I\n> actually think that the previous version with the functions was not\n> particularly bad, and I agree with Phillip that the previous version's\n> main() provided nice overview of the tests and it was easier to\n> verify the independence between each testcase.\n\nEach test uses its own strbuf and the t_ functions don't use global or\nstatic variables, so how does the doubt about their independence creep\nin?\n\nWith tests specified as above I now notice that we never check the\nresulting string.  t_addch and t_addstr check the tail matches their\nargument, but strbuf_addch and strbuf_addstr could overwrite the old\ncontent and we wouldn't notice.  Perhaps it's not worth checking, but\nnow that tests look more like test_expect_success and all steps are\nvisible I somehow miss the equivalent of test_cmp; didn't see that\nin the original version.\n\n> Perhaps, the code snippets inside the functions are small enough to\n> perceive TEST_RUN() as more convenient than TEST() in this test, but,\n> for future reference, I definitely don't think TEST_RUN() should be\n> looked at as a replacement for TEST(), and more like 'when we have to\n> use macro magic which requires us to use internal test__run_*\n> functions, using TEST_RUN() is more convenient. Patch [3/6] is a good> example of that.\n\nSure, not all tests will be improved by using TEST_RUN.  If TEST fits\nbetter, use it.\n\n> But, I also don't mind if patches 4, 5, or 6 get\n> merged as I don't see any difference between using TEST_RUN() or\n> TEST() in those patches, besides moving everything inside main().\n\nThe difference is that in the original version test description and\ndefinition are separated, only linked by a function name.  The new\nversion brings them together and does away with function name.  A small\nchange, for sure, just to get rid of the artificial divide and the need\nfor that link.\n\nRené\n"},{"id":"498011","messageId":"D2FLDECC9DTC.3KYX81M4JKOAR@gmail.com","threadId":"61700","inReplyTo":"c690e0ad-21e1-4b51-b594-70f4b2527df8@web.de","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Ghanshyam Thakkar","fromEmail":"shyamthakkar001@gmail.com","sentAt":"2024-07-03T03:42:39Z","receivedAt":"2024-07-03T03:42:44Z","isPatch":true,"sender":{"key":"shyamthakkar001@gmail.com","avatar":"https://avatars.githubusercontent.com/u/72698233?v=4"},"body":"René Scharfe <l.s.r@web.de> wrote:\n> Am 02.07.24 um 19:29 schrieb Ghanshyam Thakkar:\n> > Josh Steadmon <steadmon@google.com> wrote:\n> >>> -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n> >>> -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n> >>> -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> >>> -\t     \"strbuf_addch appends to initial value\");\n> >>> -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n> >>> -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n> >>> -\t     \"strbuf_addstr appends string to initial value\");\n> >>> +\n> >>> +\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n> >>> +\t\tstruct strbuf sb = STRBUF_INIT;\n> >>> +\t\tt_addch(&sb, 'a');\n> >>> +\t\tt_release(&sb);\n> >>> +\t}\n> >>> +\n> >>> +\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n> >>> +\t\tstruct strbuf sb = STRBUF_INIT;\n> >>> +\t\tt_addch(&sb, '\\0');\n> >>> +\t\tt_release(&sb);\n> >>> +\t}\n> >>> +\n> >>> +\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n> >>> +\t\tstruct strbuf sb = STRBUF_INIT;\n> >>> +\t\tt_addstr(&sb, \"initial value\");\n> >>> +\t\tt_addch(&sb, 'a');\n> >>> +\t\tt_release(&sb);\n> >>> +\t}\n> >>> +\n> >>> +\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n> >>> +\t\tstruct strbuf sb = STRBUF_INIT;\n> >>> +\t\tt_addstr(&sb, \"hello there\");\n> >>> +\t\tt_release(&sb);\n> >>> +\t}\n> >>> +\n> >>> +\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n> >>> +\t\tstruct strbuf sb = STRBUF_INIT;\n> >>> +\t\tt_addstr(&sb, \"initial value\");\n> >>> +\t\tt_addstr(&sb, \"hello there\");\n> >>> +\t\tt_release(&sb);\n> >>> +\t}\n> >>>\n> >>>  \treturn test_done();\n> >>>  }\n> >>> --\n> >>> 2.45.2\n> >>\n> >> I think this commit in particular shows how TEST_RUN() is more\n> >> convenient than TEST(). (Although, arguably we shouldn't have allowed\n> >> the setup() + callback situation to start with.)\n> >\n> > Could you expand a bit on why the setup() + callback thing shouldn't be\n> > allowed? I think it is a nice way of avoiding boilerplate and having\n> > independent state. And, I see the true potential of TEST_RUN() in\n> > testcases defined through macros rather than replacing functions. I\n> > actually think that the previous version with the functions was not\n> > particularly bad, and I agree with Phillip that the previous version's\n> > main() provided nice overview of the tests and it was easier to\n> > verify the independence between each testcase.\n>\n> Each test uses its own strbuf and the t_ functions don't use global or\n> static variables, so how does the doubt about their independence creep\n> in?\n\nAh, apologies. I should clarify that I meant in general terms about the\nfuture uses of TEST_RUN() and not about this particular patch. But I see\nit being less of a problem now that I think about it more. And for the\nrecord, I see no problems in this patch. But on a side note, with what\nPhillip was suggesting to remove having TEST_RUN() inside if(), it\nwould definitely make verifying state independence more harder.\n\n<snip>\n> > But, I also don't mind if patches 4, 5, or 6 get\n> > merged as I don't see any difference between using TEST_RUN() or\n> > TEST() in those patches, besides moving everything inside main().\n>\n> The difference is that in the original version test description and\n> definition are separated, only linked by a function name. The new\n> version brings them together and does away with function name. A small\n> change, for sure, just to get rid of the artificial divide and the need\n> for that link.\n\nYeah, but I didn't mind that divide (and I don't mind bringing them\ntogether either). :)\n\nThanks.\n"},{"id":"498081","messageId":"c2de7f4e-e5dc-4324-8238-1d06795a2107@gmail.com","threadId":"61700","inReplyTo":"e4da5c4d-2900-4d62-b855-7c4382a2b949@web.de","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-04T13:09:40Z","receivedAt":"2024-07-04T13:09:43Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/07/2024 21:55, René Scharfe wrote:\n> Am 02.07.24 um 17:14 schrieb phillip.wood123@gmail.com:\n>> Hi Josh and René\n>>\n>> On 01/07/2024 20:58, Josh Steadmon wrote:\n>>> On 2024.06.29 17:47, René Scharfe wrote:\n>>>\n>>> I think this commit in particular shows how TEST_RUN() is more\n>>> convenient than TEST(). (Although, arguably we shouldn't have allowed\n>>> the setup() + callback situation to start with.)\n>>\n>> I think the counterargument to that is that using TEST_RUN() makes\n>> the tests noisier and more error prone because each one has to be\n>> wrapped in an if() statement and has more boiler plate initializing\n>> and freeing the strbuf rather than getting that for free by calling\n>> the test function via setup().\n> \n> I guess these are two sides of the same coin.  Explicit initialization\n> and cleanup is closer to what real strbuf users do, more idiomatic.\n> Which helps to see what the tests are actually doing.\n> \n> The wrapping if is less annoying than a wrapping function that I have\n> to name and call.  The condition is just !ctx.skip_all, though, (for\n> now at least) so we could do something like:\n> \n>     #define TEST_START(...) if (!TEST_RUN(__VA_ARGS__)) return test_done()\n> \n> ... and tests could look like that then:\n> \n>     TEST_START(\"this and that\");\n>     check(this);\n>     check(that);\n> \n>     TEST_START(\"whatever\");\n>     check(whatever);\n> \n> Perhaps a bit too much magic, though?\n\nI think so. It's an interesting idea, but at some point someone will \nprobably want to be able to run a subset of the tests in a file like \n\"--run\" does for the integration tests so I don't think we want to \nassume we can skip all the tests just because any particular test should \nbe skipped.\n\nBest Wishes\n\nPhillip\n\n> René\n"},{"id":"498104","messageId":"8802b3ab-9986-47bd-be80-51ac599b2892@gmail.com","threadId":"61700","inReplyTo":"97390954-49bc-48c4-bab1-95be10717aca@web.de","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-05T09:42:29Z","receivedAt":"2024-07-05T09:42:32Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 02/07/2024 21:55, René Scharfe wrote:\n> Am 02.07.24 um 17:13 schrieb phillip.wood123@gmail.com:\n>> On 29/06/2024 16:43, René Scharfe wrote:\n>>> The macro TEST only allows defining a test that consists of a\n>>> single expression.  Add the new macro, TEST_RUN, which provides a\n>>> way to define unit tests that are made up of one or more\n>>> statements.  A test started with it implicitly ends when the next\n>>> test is started or test_done() is called.\n>>>\n>>> TEST_RUN allows defining self-contained tests en bloc, a bit like\n>>> test_expect_success does for regular tests.  Unlike TEST it does\n>>> not require defining wrapper functions for test statements.\n>>\n>> There are pros and cons to not requiring one function per test. It\n>> can be a pain to have to write separate functions but it keeps each\n>> test self contained which hopefully makes it harder to have\n>> accidental dependencies between tests. Having separate functions for\n>> each test makes it easy to initialize and free resources for every\n>> test by writing a setup() function that initializes the resources,\n>> calls the test function and then frees the resources.\n> \n> Right.  We should use TEST and TEST_RUN when appropriate.\n> \n>> The changes in patch 6 to use TEST_RUN() mean that each test now has\n>> more boilerplate to initialize and free the strbuf. > This makes them more similar to strbuf usage in the wild.  Using\n> the API idiomatically just makes more sense to me.\n\nI see what you mean. I think it only looks idiomatic if you're already \nfamiliar with the api though as the test bodies call wrappers rather \nthan using the strbuf api directly. I think that reduces its value as an \nexample of idomatic usage for someone who is not familiar with the \nstrbuf api.\n\n>  Not hiding\n> initialization and release makes the tests visibly independent.\n> This is not enforced by TEST_RUN, but made possible by it.\n> \n>> Having each test in its own function also makes main() shorter and\n>> which means can quickly get an overview of all the test cases from\n>> it.\n> \n> That's true, now you need to grep for TEST_RUN to get such an\n> overview.\n> \n> On the other hand I find the start of the description in TEST\n> invocations somewhat hard to locate, as they are not vertically\n> aligned due to the preceding variable-length function name.  Just\n> saying..\n\nYes I really wanted the first argument of TEST to be the description but \nthat isn't easy to do while supporting printf style format strings.\n\n>>> +int test__run(const char *location, const char *format, ...)\n>>> +{\n>>> +    va_list ap;\n>>> +    char *desc;\n>>> +\n>>> +    test__run_maybe_end();\n>>> +\n>>> +    va_start(ap, format);\n>>> +    desc = xstrvfmt(format, ap);\n>>\n>> This uses an strbuf under the hood. So far we've avoided doing that\n>> as we want to be able to test the strbuf implementation with this\n>> test framework. We don't need to support arbitrary length strings\n>> here so we could use a fixed array and xsnprinf() instead.\n> \n> Fair point.  xsnprinf() might be a bit too strict, as it doesn't\n> handle short buffers gracefully.  Perhaps that's OK; a developer\n> getting hit by that could simply increase the buffer size.\n\nI think so.\n\n> We could also let xstrvfmt() call vsnprintf(3) directly.  The code\n> duplication would be a bit grating, but perhaps there's some good\n> base function hidden in there somewhere.\n\nOh, interesting - maybe something like\n\nchar* xstrvfmt(const char *fmt, ...)\n{\n\tva_list ap, aq;\n\n\tva_start(ap, fmt);\n\tva_copy(aq, ap);\n\tlen = vnsprintf(NULL, 0, fmt, ap);\n\tif (len < 0)\n\t\tBUG(...)\n\tbuf = xmalloc(len + 1);\n\tif (vnsprintf(buf, len + 1, fmt, aq) != len)\n\t\tBUG(...)\n\tva_end(aq);\n\tva_end(ap);\n\n\treturn buf;\n}\n\n>> Looking ahead the plan seems to be to change most instances of TEST()\n>> to TEST_RUN(). If we are going to go that way perhaps we should steal\n>> TEST() for this macro and rename the existing TEST() macro.\n> \n> Not my plan, at least -- I'm content with just having the *ability*\n> to keep all parts of a test together.\n\nThat sounds sensible to me\n\n> +\tif (TEST_RUN(\"static initialization works\")) {\n> +\t\tstruct strbuf buf = STRBUF_INIT;\n> +\t\tif (!check_uint(buf.len, ==, 0) ||\n> +\t\t    !check_uint(buf.alloc, ==, 0) ||\n> +\t\t    !check_char(buf.buf[0], ==, '\\0'))\n> +\t\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n> +\t}\n\nthat's a nice use of test_skip_all()\n\n>> I'm not very enthusiastic about requiring the test author to wrap\n>> TEST_RUN() in an if() statement rather than just doing that for them.\n>> It makes it explicit but from the test author's point of view it just\n>> feels like pointless boilerplate.\n> \n> Hmm.  We can add more magic, but I suspect that it might confuse\n> developers and editors.\n\nTo me its confusing to have to wrap TEST_RUN() in an if() statement \nuntil one realizes that the test might be skipped. If we document that \nthe test body should be enclosed in braces I don't think it should \nconfuse developers or editors and will keep the tests a bit cleaner.\n\nBest Wishes\n\nPhillip\n"},{"id":"498112","messageId":"6c83357a-825f-49d9-8cc2-e81415e8010d@web.de","threadId":"61700","inReplyTo":"8802b3ab-9986-47bd-be80-51ac599b2892@gmail.com","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-05T18:01:44Z","receivedAt":"2024-07-05T18:01:52Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 05.07.24 um 11:42 schrieb phillip.wood123@gmail.com:\n> On 02/07/2024 21:55, René Scharfe wrote:\n>> Am 02.07.24 um 17:13 schrieb phillip.wood123@gmail.com:\n>>\n>>> The changes in patch 6 to use TEST_RUN() mean that each test now has\n>>> more boilerplate to initialize and free the strbuf.\n>>\n>> This makes them more similar to strbuf usage in the wild.  Using\n>> the API idiomatically just makes more sense to me.\n>\n> I see what you mean. I think it only looks idiomatic if you're\n> already familiar with the api though as the test bodies call wrappers\n> rather than using the strbuf api directly. I think that reduces its\n> value as an example of idomatic usage for someone who is not familiar\n> with the strbuf api.\n\nIn early versions I used the original names by adding these just before\nmain():\n\n   #define strbuf_addch(x, y)  t_addch(x, y)\n   #define strbuf_addstr(x, y) t_addstr(x, y)\n   #define strbuf_release(x)   t_release(x)\n\nThis allowed normal looking code to be used in tests, with checks\ninjected behind the scenes.  Rejected it for v1 because it offers no\nstructural improvements, just optics.  It does allow to forget the\nchecked versions when writing tests, though, so perhaps it's still\nworth doing.\n\n>> We could also let xstrvfmt() call vsnprintf(3) directly.  The code\n>> duplication would be a bit grating, but perhaps there's some good\n>> base function hidden in there somewhere.\n>\n> Oh, interesting - maybe something like\n>\n> char* xstrvfmt(const char *fmt, ...)\n> {\n>     va_list ap, aq;\n>\n>     va_start(ap, fmt);\n>     va_copy(aq, ap);\n>     len = vnsprintf(NULL, 0, fmt, ap);\n>     if (len < 0)\n>         BUG(...)\n>     buf = xmalloc(len + 1);\n>     if (vnsprintf(buf, len + 1, fmt, aq) != len)\n>         BUG(...)\n>     va_end(aq);\n>     va_end(ap);\n>\n>     return buf;\n> }\n\nYes.  Though the current version allocates 65 bytes in the first try\nand only needs to call vnsprintf(3) once if the output fits in.  No\nlonger doing that might affect the performance of some callers in a\nnoticeable way.\n\n>>> I'm not very enthusiastic about requiring the test author to wrap\n>>> TEST_RUN() in an if() statement rather than just doing that for them.\n>>> It makes it explicit but from the test author's point of view it just\n>>> feels like pointless boilerplate.\n>>\n>> Hmm.  We can add more magic, but I suspect that it might confuse\n>> developers and editors.\n>\n> To me its confusing to have to wrap TEST_RUN() in an if() statement\n> until one realizes that the test might be skipped. If we document\n> that the test body should be enclosed in braces I don't think it\n> should confuse developers or editors and will keep the tests a bit\n> cleaner.\n\nYou don't need braces in either case.  I.e. something like\n\n\tif (TEST_RUN(\"foo\"))\n\t\tfoo();\n\nworks fine.  And\n\n\t#define IF_TEST_RUN(...) if (TEST_RUN(__VA_ARGS__))\n\tIF_TEST_RUN(\"foo\")\n\t\tfoo();\n\nworks fine as well.\n\nThe confusion that I'm afraid of is that we'd effectively invent a new\ncontrol flow keyword here, which is unusual.  There are precedents,\nthough: foreach macros like for_each_string_list_item.  We tell\nclang-format about them using its option ForEachMacros.  I see it also\nhas an option IfMacros since version 13 (unused by us so far).  Hmm.\n\nRené\n"},{"id":"498189","messageId":"62d221cc-532a-4a6d-8e96-b5a246ddeb1b@web.de","threadId":"61700","inReplyTo":"6c83357a-825f-49d9-8cc2-e81415e8010d@web.de","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-07T07:20:56Z","receivedAt":"2024-07-07T07:21:10Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 05.07.24 um 20:01 schrieb René Scharfe:\n> Am 05.07.24 um 11:42 schrieb phillip.wood123@gmail.com:\n>> On 02/07/2024 21:55, René Scharfe wrote:\n>>> Am 02.07.24 um 17:13 schrieb phillip.wood123@gmail.com:\n>>>\n>>>> I'm not very enthusiastic about requiring the test author to wrap\n>>>> TEST_RUN() in an if() statement rather than just doing that for them.\n>>>> It makes it explicit but from the test author's point of view it just\n>>>> feels like pointless boilerplate.\n>>>\n>>> Hmm.  We can add more magic, but I suspect that it might confuse\n>>> developers and editors.\n>>\n>> To me its confusing to have to wrap TEST_RUN() in an if() statement\n>> until one realizes that the test might be skipped. If we document\n>> that the test body should be enclosed in braces I don't think it\n>> should confuse developers or editors and will keep the tests a bit\n>> cleaner.\n>\n> You don't need braces in either case.  I.e. something like\n>\n> \tif (TEST_RUN(\"foo\"))\n> \t\tfoo();\n>\n> works fine.  And\n>\n> \t#define IF_TEST_RUN(...) if (TEST_RUN(__VA_ARGS__))\n> \tIF_TEST_RUN(\"foo\")\n> \t\tfoo();\n>\n> works fine as well.\n>\n> The confusion that I'm afraid of is that we'd effectively invent a new\n> control flow keyword here, which is unusual.  There are precedents,\n> though: foreach macros like for_each_string_list_item.  We tell\n> clang-format about them using its option ForEachMacros.  I see it also\n> has an option IfMacros since version 13 (unused by us so far).  Hmm.\n\nHmm, again.  I can see the appeal.  How to call it?  Given that the\nforeach macros have lower-case names, perhaps to indicate that they act\nas control flow statements, not function-like macro calls, would we want\nlower case here as well?\n\n\t#define test(...) if (TEST_RUN(__VA_ARGS__))\n\n\t        test (\"passing test\")\n\t                check(1);\n\n\t\ttest (\"split single item\") {\n\t\t\tstruct strvec vec = STRVEC_INIT;\n\t\t\tstrvec_split(&vec, \"foo\");\n\t\t\tcheck_strvec(&vec, \"foo\", NULL);\n\t\t\tstrvec_clear(&vec);\n\t\t}\n\nCan't get much cleaner than that.  Requires learning that this macro is\nnot function-like, but it's probably not too much to ask.\n\nRené\n"},{"id":"498266","messageId":"9f63d125-b1ee-4b07-b0c7-a3c98dd31f90@gmail.com","threadId":"61700","inReplyTo":"6c83357a-825f-49d9-8cc2-e81415e8010d@web.de","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-08T15:12:34Z","receivedAt":"2024-07-08T15:12:34Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 05/07/2024 19:01, René Scharfe wrote:\n> Am 05.07.24 um 11:42 schrieb phillip.wood123@gmail.com:\n>> On 02/07/2024 21:55, René Scharfe wrote:\n>>> Am 02.07.24 um 17:13 schrieb phillip.wood123@gmail.com:\n>>>\n>>>> The changes in patch 6 to use TEST_RUN() mean that each test now has\n>>>> more boilerplate to initialize and free the strbuf.\n>>>\n>>> This makes them more similar to strbuf usage in the wild.  Using\n>>> the API idiomatically just makes more sense to me.\n>>\n>> I see what you mean. I think it only looks idiomatic if you're\n>> already familiar with the api though as the test bodies call wrappers\n>> rather than using the strbuf api directly. I think that reduces its\n>> value as an example of idomatic usage for someone who is not familiar\n>> with the strbuf api.\n> \n> In early versions I used the original names by adding these just before\n> main():\n> \n>     #define strbuf_addch(x, y)  t_addch(x, y)\n>     #define strbuf_addstr(x, y) t_addstr(x, y)\n>     #define strbuf_release(x)   t_release(x)\n> \n> This allowed normal looking code to be used in tests, with checks\n> injected behind the scenes.  Rejected it for v1 because it offers no\n> structural improvements, just optics.  It does allow to forget the\n> checked versions when writing tests, though, so perhaps it's still\n> worth doing.\n\nIt also obscures what the test is doing though. It's nice if unit tests \nshow how to use an API but I don't think we should let that be our \noverriding concern.\n\n>>> We could also let xstrvfmt() call vsnprintf(3) directly.  The code\n>>> duplication would be a bit grating, but perhaps there's some good\n>>> base function hidden in there somewhere.\n>>\n>> Oh, interesting - maybe something like\n>>\n>> char* xstrvfmt(const char *fmt, ...)\n>> {\n>>      va_list ap, aq;\n>>\n>>      va_start(ap, fmt);\n>>      va_copy(aq, ap);\n>>      len = vnsprintf(NULL, 0, fmt, ap);\n>>      if (len < 0)\n>>          BUG(...)\n>>      buf = xmalloc(len + 1);\n>>      if (vnsprintf(buf, len + 1, fmt, aq) != len)\n>>          BUG(...)\n>>      va_end(aq);\n>>      va_end(ap);\n>>\n>>      return buf;\n>> }\n> \n> Yes.  Though the current version allocates 65 bytes in the first try\n> and only needs to call vnsprintf(3) once if the output fits in.  No\n> longer doing that might affect the performance of some callers in a\n> noticeable way.\n\nGood point\n\n>>>> I'm not very enthusiastic about requiring the test author to wrap\n>>>> TEST_RUN() in an if() statement rather than just doing that for them.\n>>>> It makes it explicit but from the test author's point of view it just\n>>>> feels like pointless boilerplate.\n>>>\n>>> Hmm.  We can add more magic, but I suspect that it might confuse\n>>> developers and editors.\n>>\n>> To me its confusing to have to wrap TEST_RUN() in an if() statement\n>> until one realizes that the test might be skipped. If we document\n>> that the test body should be enclosed in braces I don't think it\n>> should confuse developers or editors and will keep the tests a bit\n>> cleaner.\n> \n> You don't need braces in either case.  I.e. something like\n> \n> \tif (TEST_RUN(\"foo\"))\n> \t\tfoo();\n> \n> works fine.  And\n> \n> \t#define IF_TEST_RUN(...) if (TEST_RUN(__VA_ARGS__))\n> \tIF_TEST_RUN(\"foo\")\n> \t\tfoo();\n> \n> works fine as well.\n\nIndeed. The reason I suggested requiring braces was to help editors \nindent the code correctly and because it gives a nice visual cue to see \nwhat is in each test.\n\nBest Wishes\n\nPhillip\n\n> The confusion that I'm afraid of is that we'd effectively invent a new\n> control flow keyword here, which is unusual.  There are precedents,\n> though: foreach macros like for_each_string_list_item.  We tell\n> clang-format about them using its option ForEachMacros.  I see it also\n> has an option IfMacros since version 13 (unused by us so far).  Hmm.\n> \n> René\n"},{"id":"498270","messageId":"69ec31f8-2cac-48c3-a513-0f7f21607c88@gmail.com","threadId":"61700","inReplyTo":"62d221cc-532a-4a6d-8e96-b5a246ddeb1b@web.de","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-08T15:18:16Z","receivedAt":"2024-07-08T15:18:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 07/07/2024 08:20, René Scharfe wrote:\n> Hmm, again.  I can see the appeal.  How to call it?  Given that the\n> foreach macros have lower-case names, perhaps to indicate that they act\n> as control flow statements, not function-like macro calls, would we want\n> lower case here as well?\n\nI'd automatically assumed we'd want an uppercase name to signal that it \nwas a pre-processor macro but I've not really spent any time thinking \nabout it.\n\n> \t#define test(...) if (TEST_RUN(__VA_ARGS__))\n> \n> \t        test (\"passing test\")\n> \t                check(1);\n> \n> \t\ttest (\"split single item\") {\n> \t\t\tstruct strvec vec = STRVEC_INIT;\n> \t\t\tstrvec_split(&vec, \"foo\");\n> \t\t\tcheck_strvec(&vec, \"foo\", NULL);\n> \t\t\tstrvec_clear(&vec);\n> \t\t}\n> \n> Can't get much cleaner than that.\n\nYes, I like it\n\n> Requires learning that this macro is\n> not function-like, but it's probably not too much to ask.\n\nFor someone new to the project it should hopefully be pretty clear how \nto use it from the existing tests once we have a few more test files \nthat use it. Maybe an uppercase name would signal that it is special?\n\nBest Wishes\n\nPhillip\n"},{"id":"498274","messageId":"xmqq8qyblwpn.fsf@gitster.g","threadId":"61700","inReplyTo":"69ec31f8-2cac-48c3-a513-0f7f21607c88@gmail.com","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-08T15:39:16Z","receivedAt":"2024-07-08T15:39:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"phillip.wood123@gmail.com writes:\n\n> Hi René\n>\n> On 07/07/2024 08:20, René Scharfe wrote:\n>> Hmm, again.  I can see the appeal.  How to call it?  Given that the\n>> foreach macros have lower-case names, perhaps to indicate that they act\n>> as control flow statements, not function-like macro calls, would we want\n>> lower case here as well?\n>\n> I'd automatically assumed we'd want an uppercase name to signal that\n> it was a pre-processor macro but I've not really spent any time\n> thinking about it.\n>\n>> \t#define test(...) if (TEST_RUN(__VA_ARGS__))\n>> \t        test (\"passing test\")\n>> \t                check(1);\n>> \t\ttest (\"split single item\") {\n>> \t\t\tstruct strvec vec = STRVEC_INIT;\n>> \t\t\tstrvec_split(&vec, \"foo\");\n>> \t\t\tcheck_strvec(&vec, \"foo\", NULL);\n>> \t\t\tstrvec_clear(&vec);\n>> \t\t}\n>> Can't get much cleaner than that.\n>\n> Yes, I like it\n\nYeah, if you squint your eyes hard, it starts to look a bit like\ntest_expect_success we use in the test suite ;-)\n\nIsn't this introducing a new control structure to the language?\n\nA macro that is a conditional switch (aka \"if\"-like statement),\nhaving \"if\" in the name somewhere, and a macro that wrapts a loop\naround a block (aka \"for/while\" like statement), having \"for\" in the\nname somewhere, might be less confusing for the uninitiated.\n\n> For someone new to the project it should hopefully be pretty clear how\n> to use it from the existing tests once we have a few more test files\n> that use it. Maybe an uppercase name would signal that it is special?\n\nAs to the cases, I personally prefer lowercase names whose semantics\nis very clear, just like e.g., kh_foreach() makes it clear with\n\"foreach\" that it iterates over the set.  But the above \"test\" may\nnot qualify---it is not making it sufficiently clear what control\nstructure it is creating with its name.\n\nThanks.\n"},{"id":"498302","messageId":"tswyfparvchgi7qxrjxbx4eb7cohypzekjqzbnkbffsesaiazs@vtewtz7o6twi","threadId":"61700","inReplyTo":"D2F8BS39IFC4.WJ9TNK3IDFBV@gmail.com","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-07-08T18:11:08Z","receivedAt":"2024-07-08T18:11:15Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.07.02 22:59, Ghanshyam Thakkar wrote:\n> Josh Steadmon <steadmon@google.com> wrote:\n> > > -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n> > > -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n> > > -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> > > -\t     \"strbuf_addch appends to initial value\");\n> > > -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n> > > -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n> > > -\t     \"strbuf_addstr appends string to initial value\");\n> > > +\n> > > +\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n> > > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > > +\t\tt_addch(&sb, 'a');\n> > > +\t\tt_release(&sb);\n> > > +\t}\n> > > +\n> > > +\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n> > > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > > +\t\tt_addch(&sb, '\\0');\n> > > +\t\tt_release(&sb);\n> > > +\t}\n> > > +\n> > > +\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n> > > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > > +\t\tt_addstr(&sb, \"initial value\");\n> > > +\t\tt_addch(&sb, 'a');\n> > > +\t\tt_release(&sb);\n> > > +\t}\n> > > +\n> > > +\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n> > > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > > +\t\tt_addstr(&sb, \"hello there\");\n> > > +\t\tt_release(&sb);\n> > > +\t}\n> > > +\n> > > +\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n> > > +\t\tstruct strbuf sb = STRBUF_INIT;\n> > > +\t\tt_addstr(&sb, \"initial value\");\n> > > +\t\tt_addstr(&sb, \"hello there\");\n> > > +\t\tt_release(&sb);\n> > > +\t}\n> > > \n> > >  \treturn test_done();\n> > >  }\n> > > --\n> > > 2.45.2\n> >\n> > I think this commit in particular shows how TEST_RUN() is more\n> > convenient than TEST(). (Although, arguably we shouldn't have allowed\n> > the setup() + callback situation to start with.)\n> \n> Could you expand a bit on why the setup() + callback thing shouldn't be\n> allowed? I think it is a nice way of avoiding boilerplate and having\n> independent state.\n\nIt's a matter of taste rather than strict principles, I suppose, but I\nthink that test cases should focus on being readable rather than\navoiding duplication. Production code can follow a Don't Repeat Yourself\nphilosophy, but when a test breaks, it's better to be able to look at\nonly the failing test and quickly see what's wrong, rather than having\nto work out which functions call which callbacks and what gets verified\nwhere. Also, since we don't have tests for our tests, it's important\nthat the tests are easy for people to manually verify them.\n\nI also agree with René's point about the test cases matching real-world\nAPI usage.\n\nI do agree that having separate functions improves isolation, but\nhopefully we can encourage test authors to define local variables in\ntheir TEST_RUN blocks.\n\n> And, I see the true potential of TEST_RUN() in\n> testcases defined through macros rather than replacing functions. I\n> actually think that the previous version with the functions was not\n> particularly bad, and I agree with Phillip that the previous version's\n> main() provided nice overview of the tests and it was easier to\n> verify the independence between each testcase.\n> \n> Perhaps, the code snippets inside the functions are small enough to\n> perceive TEST_RUN() as more convenient than TEST() in this test, but,\n> for future reference, I definitely don't think TEST_RUN() should be\n> looked at as a replacement for TEST(), and more like 'when we have to\n> use macro magic which requires us to use internal test__run_*\n> functions, using TEST_RUN() is more convenient'. Patch [3/6] is a good\n> example of that. But, I also don't mind if patches 4, 5, or 6 get\n> merged as I don't see any difference between using TEST_RUN() or\n> TEST() in those patches, besides moving everything inside main().\n> \n> Thanks.\n"},{"id":"498324","messageId":"D2KHTOXCNFOI.Y1UIN4IT8APB@gmail.com","threadId":"61700","inReplyTo":"tswyfparvchgi7qxrjxbx4eb7cohypzekjqzbnkbffsesaiazs@vtewtz7o6twi","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Ghanshyam Thakkar","fromEmail":"shyamthakkar001@gmail.com","sentAt":"2024-07-08T21:59:11Z","receivedAt":"2024-07-08T21:59:17Z","isPatch":true,"sender":{"key":"shyamthakkar001@gmail.com","avatar":"https://avatars.githubusercontent.com/u/72698233?v=4"},"body":"Josh Steadmon <steadmon@google.com> wrote:\n> On 2024.07.02 22:59, Ghanshyam Thakkar wrote:\n> > Josh Steadmon <steadmon@google.com> wrote:\n> > > I think this commit in particular shows how TEST_RUN() is more\n> > > convenient than TEST(). (Although, arguably we shouldn't have allowed\n> > > the setup() + callback situation to start with.)\n> > \n> > Could you expand a bit on why the setup() + callback thing shouldn't be\n> > allowed? I think it is a nice way of avoiding boilerplate and having\n> > independent state.\n>\n> It's a matter of taste rather than strict principles, I suppose, but I\n> think that test cases should focus on being readable rather than\n> avoiding duplication. Production code can follow a Don't Repeat Yourself\n> philosophy, but when a test breaks, it's better to be able to look at\n> only the failing test and quickly see what's wrong, rather than having\n> to work out which functions call which callbacks and what gets verified\n> where. Also, since we don't have tests for our tests, it's important\n> that the tests are easy for people to manually verify them.\n\nMakes sense. But the manner in which callbacks are used in these tests,\nwhich usually only initialize a certain datastructure and then release them at\nthe end, I fail to see them as more harder to manually verify. But it is\nvery easy to stretch the line and perhaps do more than initialization in\nthe setup(), so I can see how disallowing them would get rid of the need of\njudging every time wether they are readable or not. Maybe a 'best\npractices' document would help in that regard? \n\n> I also agree with René's point about the test cases matching real-world\n> API usage.\n>\n> I do agree that having separate functions improves isolation, but\n> hopefully we can encourage test authors to define local variables in\n> their TEST_RUN blocks.\n\nMakes sense.\n\nThanks.\n"},{"id":"498449","messageId":"35f828d3-6d3d-4b86-b6bb-a35753d91b9b@gmail.com","threadId":"61700","inReplyTo":"d768f700-654a-4030-bd45-e8e3dea351b5@gmail.com","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-10T13:55:58Z","receivedAt":"2024-07-10T13:56:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/07/2024 16:14, phillip.wood123@gmail.com wrote:\n> Getting rid of the untyped test arguments is \n> definitely a benefit of this approach.\n\nThat got me thinking how we might make type-safe setup()\nfunctions. The diff below shows how we could define a macro to\ngenerate the functions. DEFINE_SETUP_FN(char, ch) defines setup_ch()\nthat takes a test function and a char that is passed to the test with\nthe initialized strbuf. I'm not sure that's the way we want to go for\nthis test file but I thought I'd post it in case it is useful for\nfuture tests.\n\nBest Wishes\n\nPhillip\n\n---- >8 ----\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex 6027dafef70..8fc9a8b38df 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -1,27 +1,60 @@\n  #include \"test-lib.h\"\n  #include \"strbuf.h\"\n  \n-/* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, const void*),\n-\t\t  const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n-/* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, const void*),\n-\t\t\t    const char *init_str, const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tstrbuf_addstr(&buf, init_str);\n-\tcheck_uint(buf.len, ==, strlen(init_str));\n-\tf(&buf, data);\n+/*\n+ * Define a type safe wrapper function that supplies test functions\n+ * with an initialized strbuf populated with an optional string and\n+ * some data and then frees the strbuf when the test function\n+ * returns. For example given the test function\n+ *\n+ *      t_foo(struct strbuf *buf, struct foo *data)\n+ *\n+ * the type safe wrapper function\n+ *\n+ *     setup_foo(void(*)(struct strbuf*, const struct foo*),\n+ *     \t\t const char *init_str, const struct foo*)\n+ *\n+ * can be defined with\n+ *\n+ *     DEFINE_SETUP_FN(const struct foo*, foo)\n+ *\n+ * and used to run t_foo() with\n+ *\n+ *     TEST(setup_foo(t_foo, \"initial string\", &my_foo), \"test foo\");\n+ */\n+#define DEFINE_SETUP_FN(type, suffix) \\\n+\tstatic void marshal_##suffix(void(*test_fn)(void),\t\t     \\\n+\t\t\t\t      struct strbuf *buf, const void *ctx)   \\\n+\t{\t\t\t\t\t\t\t\t     \\\n+\t\ttype data = *(type *)ctx;\t\t\t\t     \\\n+\t\t((void(*)(struct strbuf*, type)) test_fn)(buf, data);\t     \\\n+\t}\t\t\t\t\t\t\t\t     \\\n+\t\t\t\t\t\t\t\t\t     \\\n+\tstatic void setup_##suffix(void(*test_fn)(struct strbuf*, type),     \\\n+\t\t\t\t   const char *init_str, type data)\t     \\\n+\t{\t\t\t\t\t\t\t\t     \\\n+\t\tvoid *ctx = &data;\t\t\t\t\t     \\\n+\t\tdo_setup(init_str, (void(*)(void)) test_fn, ctx,\t     \\\n+\t\t\t marshal_##suffix);\t\t\t\t     \\\n+\t}\n+\n+/*\n+ * Helper function for DEFINE_SETUP_FN() that initializes the strbuf,\n+ * calls the test function and releases the strbuf\n+ */\n+static void do_setup(const char* init_str, void(*f)(void), const void *ctx,\n+\t\t     void(*marshal)(void(*)(void), struct strbuf*, const void*))\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n+\tif (init_str) {\n+\t\tstrbuf_addstr(&buf, init_str);\n+\t\tif (!check_uint(buf.len, ==, strlen(init_str))) {\n+\t\t\tstrbuf_release(&buf);\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\tmarshal(f, &buf, ctx);\n  \tstrbuf_release(&buf);\n  \tcheck_uint(buf.len, ==, 0);\n  \tcheck_uint(buf.alloc, ==, 0);\n@@ -66,10 +99,9 @@ static void t_dynamic_init(void)\n  \tstrbuf_release(&buf);\n  }\n  \n-static void t_addch(struct strbuf *buf, const void *data)\n+DEFINE_SETUP_FN(char, ch)\n+static void t_addch(struct strbuf *buf, char ch)\n  {\n-\tconst char *p_ch = data;\n-\tconst char ch = *p_ch;\n  \tsize_t orig_alloc = buf->alloc;\n  \tsize_t orig_len = buf->len;\n  \n@@ -85,9 +117,9 @@ static void t_addch(struct strbuf *buf, const void *data)\n  \tcheck_char(buf->buf[buf->len], ==, '\\0');\n  }\n  \n-static void t_addstr(struct strbuf *buf, const void *data)\n+DEFINE_SETUP_FN(const char*, str)\n+static void t_addstr(struct strbuf *buf, const char *text)\n  {\n-\tconst char *text = data;\n  \tsize_t len = strlen(text);\n  \tsize_t orig_alloc = buf->alloc;\n  \tsize_t orig_len = buf->len;\n@@ -110,12 +142,12 @@ int cmd_main(int argc, const char **argv)\n  \tif (!TEST(t_static_init(), \"static initialization works\"))\n  \t\ttest_skip_all(\"STRBUF_INIT is broken\");\n  \tTEST(t_dynamic_init(), \"dynamic initialization works\");\n-\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n-\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n-\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n+\tTEST(setup_ch(t_addch, NULL, 'a'), \"strbuf_addch adds char\");\n+\tTEST(setup_ch(t_addch, NULL, '\\0'), \"strbuf_addch adds NUL char\");\n+\tTEST(setup_ch(t_addch, \"initial value\", 'a'),\n  \t     \"strbuf_addch appends to initial value\");\n-\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n-\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n+\tTEST(setup_str(t_addstr, NULL, \"hello there\"), \"strbuf_addstr adds string\");\n+\tTEST(setup_str(t_addstr, \"initial value\", \"hello there\"),\n  \t     \"strbuf_addstr appends string to initial value\");\n  \n  \treturn test_done();\n\n"},{"id":"498482","messageId":"xmqqy168uc8i.fsf@gitster.g","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"Re: [PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-10T22:13:33Z","receivedAt":"2024-07-10T22:13:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> The macro TEST only allows defining a test that consists of a single\n> expression.  This requires wrapping tests made up of one or more\n> statements in a function, which is a small, but avoidable hurdle.  This\n> series provides a new macro, TEST_RUN, that provides a way to define\n> tests without requiring to declare a function.\n>\n>   t0080: move expected output to a file\n>   unit-tests: add TEST_RUN\n>   t-ctype: use TEST_RUN\n>   t-reftable-basics: use TEST_RUN\n>   t-strvec: use TEST_RUN\n>   t-strbuf: use TEST_RUN\n>\n>  t/helper/test-example-tap.c      |  33 +++\n>  t/t0080-unit-test-output.sh      |  48 +----\n>  t/t0080/expect                   |  76 +++++++\n>  t/unit-tests/t-ctype.c           |   4 +-\n>  t/unit-tests/t-reftable-basics.c | 228 +++++++++-----------\n>  t/unit-tests/t-strbuf.c          |  79 +++----\n>  t/unit-tests/t-strvec.c          | 356 ++++++++++++++-----------------\n>  t/unit-tests/test-lib.c          |  42 +++-\n>  t/unit-tests/test-lib.h          |   8 +\n>  9 files changed, 462 insertions(+), 412 deletions(-)\n>  create mode 100644 t/t0080/expect\n\nSo, looking back the discussion list on\n\n  https://lore.kernel.org/git/85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de/\n\nany loose ends still need to be addressed?  I didn't spot any\nmyself, so I am willing to merge it to 'next' soonish, but please\nstop me if there were something I missed.\n\nThanks.\n"},{"id":"498506","messageId":"38c962cd-2691-4664-b262-6eb78b8a61b5@gmail.com","threadId":"61700","inReplyTo":"xmqqy168uc8i.fsf@gitster.g","subject":"Re: [PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-11T10:05:34Z","receivedAt":"2024-07-11T10:05:37Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 10/07/2024 23:13, Junio C Hamano wrote:\n> René Scharfe <l.s.r@web.de> writes:\n>\n> So, looking back the discussion list on\n> \n>    https://lore.kernel.org/git/85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de/\n> \n> any loose ends still need to be addressed?  I didn't spot any\n> myself, so I am willing to merge it to 'next' soonish, but please\n> stop me if there were something I missed.\n\nI thought René was planning a re-roll to avoid using xstrfmt() in Patch \n2 c.f <97390954-49bc-48c4-bab1-95be10717aca@web.de>. Also I'm not sure \nwe've reached a conclusion on whether to include the \"if\" in the macro \nor require the user to write \"if(TEST_RUN(...))\". My impression is that \nthere is a consensus building around having the macro include the \"if\" \nbut we haven't decided what to call it c.f. \n<62d221cc-532a-4a6d-8e96-b5a246ddeb1b@web.de>\n\nBest Wishes\n\nPhillip\n\n"},{"id":"498518","messageId":"xmqqikxcnet5.fsf@gitster.g","threadId":"61700","inReplyTo":"38c962cd-2691-4664-b262-6eb78b8a61b5@gmail.com","subject":"Re: [PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-11T15:12:06Z","receivedAt":"2024-07-11T15:12:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 10/07/2024 23:13, Junio C Hamano wrote:\n>> René Scharfe <l.s.r@web.de> writes:\n>>\n>> So, looking back the discussion list on\n>>    https://lore.kernel.org/git/85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de/\n>> any loose ends still need to be addressed?  I didn't spot any\n>> myself, so I am willing to merge it to 'next' soonish, but please\n>> stop me if there were something I missed.\n>\n> I thought René was planning a re-roll to avoid using xstrfmt() in\n> Patch 2 c.f <97390954-49bc-48c4-bab1-95be10717aca@web.de>. Also I'm\n> not sure we've reached a conclusion on whether to include the \"if\" in\n> the macro or require the user to write \"if(TEST_RUN(...))\". My\n> impression is that there is a consensus building around having the\n> macro include the \"if\" but we haven't decided what to call it\n> c.f. <62d221cc-532a-4a6d-8e96-b5a246ddeb1b@web.de>\n>\n> Best Wishes\n\nThanks.  Very much appreciated.\n\n"},{"id":"498519","messageId":"xmqqa5iot01s.fsf@gitster.g","threadId":"61700","inReplyTo":"xmqq8qyblwpn.fsf@gitster.g","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-11T15:34:23Z","receivedAt":"2024-07-11T15:34:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> I'd automatically assumed we'd want an uppercase name to signal that\n>> it was a pre-processor macro but I've not really spent any time\n>> thinking about it.\n>>\n>>> \t#define test(...) if (TEST_RUN(__VA_ARGS__))\n>>> \t        test (\"passing test\")\n>>> \t                check(1);\n> ...\n> Isn't this introducing a new control structure to the language?\n>\n> A macro that is a conditional switch (aka \"if\"-like statement),\n> having \"if\" in the name somewhere, and a macro that wrapts a loop\n> around a block (aka \"for/while\" like statement), having \"for\" in the\n> name somewhere, might be less confusing for the uninitiated.\n\nSo, perhaps test_if_XXXXXX() but it is not quite clear to me when\nTEST_RUN() wants to return true, so I cannot come up with an\nappropriate value to fill the XXXXXX part.  If this is about\nhonoring GIT_SKIP_TESTS or something similar, then I may suggest\ntest_if_enabled(), but that does not seem like it.  So...\n\n"},{"id":"498613","messageId":"954d67b1-8819-4bfc-ae10-f04beca5419a@gmail.com","threadId":"61700","inReplyTo":"xmqqa5iot01s.fsf@gitster.g","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-13T13:27:08Z","receivedAt":"2024-07-13T13:27:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 11/07/2024 16:34, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> So, perhaps test_if_XXXXXX() but it is not quite clear to me when\n> TEST_RUN() wants to return true, so I cannot come up with an\n> appropriate value to fill the XXXXXX part.  If this is about\n> honoring GIT_SKIP_TESTS or something similar, then I may suggest\n> test_if_enabled(), but that does not seem like it.  So...\n\nTEST_RUN() returns true if the test has not been skipped i.e. the test \nshould be run. At the moment the only way to skip a test is to call \ntest_skip_all() in a previous test. In the future I expect we'll add \nsomething like the \"--run\" option we have for the integration tests.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"498627","messageId":"xmqqsewdb8d1.fsf@gitster.g","threadId":"61700","inReplyTo":"954d67b1-8819-4bfc-ae10-f04beca5419a@gmail.com","subject":"Re: [PATCH 2/6] unit-tests: add TEST_RUN","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-13T15:48:58Z","receivedAt":"2024-07-13T15:49:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 11/07/2024 16:34, Junio C Hamano wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>> So, perhaps test_if_XXXXXX() but it is not quite clear to me when\n>> TEST_RUN() wants to return true, so I cannot come up with an\n>> appropriate value to fill the XXXXXX part.  If this is about\n>> honoring GIT_SKIP_TESTS or something similar, then I may suggest\n>> test_if_enabled(), but that does not seem like it.  So...\n>\n> TEST_RUN() returns true if the test has not been skipped i.e. the test\n> should be run. At the moment the only way to skip a test is to call\n> test_skip_all() in a previous test. In the future I expect we'll add\n> something like the \"--run\" option we have for the integration tests.\n\nThanks, so test_if_enabled() is not all that off the mark after all.\n"},{"id":"498660","messageId":"c14a4a21-9812-497f-a05c-f8e913a1cc02@web.de","threadId":"61700","inReplyTo":"38c962cd-2691-4664-b262-6eb78b8a61b5@gmail.com","subject":"Re: [PATCH 0/6] unit-tests: add and use TEST_RUN to simplify tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-14T10:35:42Z","receivedAt":"2024-07-14T10:35:54Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 11.07.24 um 12:05 schrieb Phillip Wood:\n> On 10/07/2024 23:13, Junio C Hamano wrote:\n>> René Scharfe <l.s.r@web.de> writes:\n>>\n>> So, looking back the discussion list on\n>>\n>>    https://lore.kernel.org/git/85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de/\n>>\n>> any loose ends still need to be addressed?  I didn't spot any\n>> myself, so I am willing to merge it to 'next' soonish, but please\n>> stop me if there were something I missed.\n>\n> I thought René was planning a re-roll to avoid using xstrfmt() in\n> Patch 2 c.f <97390954-49bc-48c4-bab1-95be10717aca@web.de>. Also I'm\n> not sure we've reached a conclusion on whether to include the \"if\" in\n> the macro or require the user to write \"if(TEST_RUN(...))\". My\n> impression is that there is a consensus building around having the\n> macro include the \"if\" but we haven't decided what to call it c.f.\n> <62d221cc-532a-4a6d-8e96-b5a246ddeb1b@web.de>\nRight, thanks, and I'm back now from a surprisingly exhausting business\ntrip.  Will send v3 soonish.\n\nRené\n"},{"id":"498661","messageId":"5bc6ce3e-38cb-40cd-b7fc-f88e4ca78d6a@web.de","threadId":"61700","inReplyTo":"35f828d3-6d3d-4b86-b6bb-a35753d91b9b@gmail.com","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-14T11:44:13Z","receivedAt":"2024-07-14T11:44:22Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 10.07.24 um 15:55 schrieb Phillip Wood:\n> On 02/07/2024 16:14, phillip.wood123@gmail.com wrote:\n>> Getting rid of the untyped test arguments is definitely a benefit of this approach.\n>\n> That got me thinking how we might make type-safe setup()\n> functions. The diff below shows how we could define a macro to\n> generate the functions. DEFINE_SETUP_FN(char, ch) defines setup_ch()\n> that takes a test function and a char that is passed to the test with\n> the initialized strbuf. I'm not sure that's the way we want to go for\n> this test file but I thought I'd post it in case it is useful for\n> future tests.\n\nI'm sympathetic with the idea of enhancing the weak type system of C to\navoid mistakes.  Hiding the use of void pointers behind typed wrappers\ncan be useful.  Not using void pointers is even better and clearer,\nthough, where possible.\n\nThought about using such wrappers around qsort(3) and friends long ago\nas well as parse_options() more recently, but didn't arrive at mergeable\npatches, yet.\n\nInitialization and destruction seem easy and familiar enough to not\nwarrant the use of void pointers.  No need to get fancy just for basic\nnecessities IMHO.\n\nRené\n"},{"id":"498728","messageId":"D2Q70KGM98S7.3FMTY19CAFTEM@gmail.com","threadId":"61700","inReplyTo":"35f828d3-6d3d-4b86-b6bb-a35753d91b9b@gmail.com","subject":"Re: [PATCH 6/6] t-strbuf: use TEST_RUN","fromName":"Ghanshyam Thakkar","fromEmail":"shyamthakkar001@gmail.com","sentAt":"2024-07-15T14:46:58Z","receivedAt":"2024-07-15T14:47:03Z","isPatch":true,"sender":{"key":"shyamthakkar001@gmail.com","avatar":"https://avatars.githubusercontent.com/u/72698233?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> wrote:\n> On 02/07/2024 16:14, phillip.wood123@gmail.com wrote:\n> > Getting rid of the untyped test arguments is \n> > definitely a benefit of this approach.\n>\n> That got me thinking how we might make type-safe setup()\n> functions. The diff below shows how we could define a macro to\n> generate the functions. DEFINE_SETUP_FN(char, ch) defines setup_ch()\n> that takes a test function and a char that is passed to the test with\n> the initialized strbuf. I'm not sure that's the way we want to go for\n> this test file but I thought I'd post it in case it is useful for\n> future tests.\n>\n> Best Wishes\n>\n> Phillip\n\nThis seems interesting if we want keep using setup() architecture. But a\nbit too much if we have to keep doing this in every test where we need\nsetup(). Maybe some of it can be abstracted away in test-lib? Anyway, I was\na bit surprised that we didn't just use 'const char *' instead of 'const void *',\nas we pass a string to all of them. So, I don't see the value of using 'void *'\nin the first place in this test.\n\nThanks.\n\n> ---- >8 ----\n> diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\n> index 6027dafef70..8fc9a8b38df 100644\n> --- a/t/unit-tests/t-strbuf.c\n> +++ b/t/unit-tests/t-strbuf.c\n> @@ -1,27 +1,60 @@\n> #include \"test-lib.h\"\n> #include \"strbuf.h\"\n>   \n> -/* wrapper that supplies tests with an empty, initialized strbuf */\n> -static void setup(void (*f)(struct strbuf*, const void*),\n> - const void *data)\n> -{\n> - struct strbuf buf = STRBUF_INIT;\n> -\n> - f(&buf, data);\n> - strbuf_release(&buf);\n> - check_uint(buf.len, ==, 0);\n> - check_uint(buf.alloc, ==, 0);\n> -}\n> -\n> -/* wrapper that supplies tests with a populated, initialized strbuf */\n> -static void setup_populated(void (*f)(struct strbuf*, const void*),\n> - const char *init_str, const void *data)\n> -{\n> - struct strbuf buf = STRBUF_INIT;\n> -\n> - strbuf_addstr(&buf, init_str);\n> - check_uint(buf.len, ==, strlen(init_str));\n> - f(&buf, data);\n> +/*\n> + * Define a type safe wrapper function that supplies test functions\n> + * with an initialized strbuf populated with an optional string and\n> + * some data and then frees the strbuf when the test function\n> + * returns. For example given the test function\n> + *\n> + * t_foo(struct strbuf *buf, struct foo *data)\n> + *\n> + * the type safe wrapper function\n> + *\n> + * setup_foo(void(*)(struct strbuf*, const struct foo*),\n> + * const char *init_str, const struct foo*)\n> + *\n> + * can be defined with\n> + *\n> + * DEFINE_SETUP_FN(const struct foo*, foo)\n> + *\n> + * and used to run t_foo() with\n> + *\n> + * TEST(setup_foo(t_foo, \"initial string\", &my_foo), \"test foo\");\n> + */\n> +#define DEFINE_SETUP_FN(type, suffix) \\\n> + static void marshal_##suffix(void(*test_fn)(void), \\\n> + struct strbuf *buf, const void *ctx) \\\n> + { \\\n> + type data = *(type *)ctx; \\\n> + ((void(*)(struct strbuf*, type)) test_fn)(buf, data); \\\n> + } \\\n> + \\\n> + static void setup_##suffix(void(*test_fn)(struct strbuf*, type), \\\n> + const char *init_str, type data) \\\n> + { \\\n> + void *ctx = &data; \\\n> + do_setup(init_str, (void(*)(void)) test_fn, ctx, \\\n> + marshal_##suffix); \\\n> + }\n> +\n> +/*\n> + * Helper function for DEFINE_SETUP_FN() that initializes the strbuf,\n> + * calls the test function and releases the strbuf\n> + */\n> +static void do_setup(const char* init_str, void(*f)(void), const void\n> *ctx,\n> + void(*marshal)(void(*)(void), struct strbuf*, const void*))\n> +{\n> + struct strbuf buf = STRBUF_INIT;\n> +\n> + if (init_str) {\n> + strbuf_addstr(&buf, init_str);\n> + if (!check_uint(buf.len, ==, strlen(init_str))) {\n> + strbuf_release(&buf);\n> + return;\n> + }\n> + }\n> + marshal(f, &buf, ctx);\n> strbuf_release(&buf);\n> check_uint(buf.len, ==, 0);\n> check_uint(buf.alloc, ==, 0);\n> @@ -66,10 +99,9 @@ static void t_dynamic_init(void)\n> strbuf_release(&buf);\n> }\n>   \n> -static void t_addch(struct strbuf *buf, const void *data)\n> +DEFINE_SETUP_FN(char, ch)\n> +static void t_addch(struct strbuf *buf, char ch)\n> {\n> - const char *p_ch = data;\n> - const char ch = *p_ch;\n> size_t orig_alloc = buf->alloc;\n> size_t orig_len = buf->len;\n>   \n> @@ -85,9 +117,9 @@ static void t_addch(struct strbuf *buf, const void\n> *data)\n> check_char(buf->buf[buf->len], ==, '\\0');\n> }\n>   \n> -static void t_addstr(struct strbuf *buf, const void *data)\n> +DEFINE_SETUP_FN(const char*, str)\n> +static void t_addstr(struct strbuf *buf, const char *text)\n> {\n> - const char *text = data;\n> size_t len = strlen(text);\n> size_t orig_alloc = buf->alloc;\n> size_t orig_len = buf->len;\n> @@ -110,12 +142,12 @@ int cmd_main(int argc, const char **argv)\n> if (!TEST(t_static_init(), \"static initialization works\"))\n> test_skip_all(\"STRBUF_INIT is broken\");\n> TEST(t_dynamic_init(), \"dynamic initialization works\");\n> - TEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n> - TEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n> - TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> + TEST(setup_ch(t_addch, NULL, 'a'), \"strbuf_addch adds char\");\n> + TEST(setup_ch(t_addch, NULL, '\\0'), \"strbuf_addch adds NUL char\");\n> + TEST(setup_ch(t_addch, \"initial value\", 'a'),\n> \"strbuf_addch appends to initial value\");\n> - TEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n> - TEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n> + TEST(setup_str(t_addstr, NULL, \"hello there\"), \"strbuf_addstr adds\n> string\");\n> + TEST(setup_str(t_addstr, \"initial value\", \"hello there\"),\n> \"strbuf_addstr appends string to initial value\");\n>   \n> return test_done();\n\n"},{"id":"499023","messageId":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH v2 0/6] unit-tests: add and use for_test to simplify tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:12:16Z","receivedAt":"2024-07-21T06:12:35Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Changes since v1:\n- replace \"if (TEST_RUN(...))\" with \"for_test (...)\", a macro based on the\n  keyword \"for\" that doesn't require changes to existing functions,\n- clarify the commit messages of patches 3 and 5,\n- convert the strbuf initialization tests as well.\n\n  t0080: move expected output to a file\n  unit-tests: add for_test\n  t-ctype: use for_test\n  t-reftable-basics: use for_test\n  t-strvec: use for_test\n  t-strbuf: use for_test\n\n .clang-format                    |   2 +\n t/helper/test-example-tap.c      |  33 +++\n t/t0080-unit-test-output.sh      |  48 +----\n t/t0080/expect                   |  76 +++++++\n t/unit-tests/t-ctype.c           |   4 +-\n t/unit-tests/t-reftable-basics.c | 228 +++++++++-----------\n t/unit-tests/t-strbuf.c          | 122 ++++++-----\n t/unit-tests/t-strvec.c          | 356 ++++++++++++++-----------------\n t/unit-tests/test-lib.h          |  19 ++\n 9 files changed, 454 insertions(+), 434 deletions(-)\n create mode 100644 t/t0080/expect\n\nRange-Diff gegen v1:\n1:  6efe5a37f0 = 1:  5faabaea54 t0080: move expected output to a file\n2:  8297c2b121 < -:  ---------- unit-tests: add TEST_RUN\n-:  ---------- > 2:  d4f9fa0938 unit-tests: add for_test\n3:  ec5599906d ! 3:  a7cd5a2a3a t-ctype: use TEST_RUN\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-ctype: use TEST_RUN\n    +    t-ctype: use for_test\n\n    -    Use the macro TEST_RUN instead of the internal functions\n    -    test__run_begin() and test__run_end().\n    +    Use the documented macro for_test instead of the internal functions\n    +    test__run_begin() and test__run_end(), which are supposed to be private\n    +    to the unit test framework.\n\n      ## t/unit-tests/t-ctype.c ##\n     @@\n    @@ t/unit-tests/t-ctype.c\n      \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n     -\tint skip = test__run_begin(); \\\n     -\tif (!skip) { \\\n    -+\tif (TEST_RUN(#class \" works\")) { \\\n    ++\tfor_test (#class \" works\") { \\\n      \t\tfor (int i = 0; i < 256; i++) { \\\n      \t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n      \t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n4:  e589468f98 ! 4:  cc07910f88 t-reftable-basics: use TEST_RUN\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-reftable-basics: use TEST_RUN\n    +    t-reftable-basics: use for_test\n\n         The macro TEST takes a single expression.  If a test requires multiple\n         statements then they need to be placed in a function that's called in\n         the TEST expression.\n\n         Remove the overhead of defining and calling single-use functions by\n    -    using TEST_RUN instead.\n    +    using for_test instead.\n\n         Run the tests in the order of definition.  We can reorder them like that\n         because they are independent.  Technically this changes the output, but\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\t\tstruct integer_needle_lesseq_args args = {\n     -\t\t\t.haystack = haystack,\n     -\t\t\t.needle = testcases[i].needle,\n    -+\tif (TEST_RUN(\"binary search with binsearch works\")) {\n    ++\tfor_test (\"binary search with binsearch works\") {\n     +\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n     +\t\tstruct {\n     +\t\t\tint needle;\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n     -\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n     -\tconst char *c[] = { \"a\", \"b\", NULL };\n    -+\tif (TEST_RUN(\"names_length retuns size of a NULL-terminated string array\")) {\n    ++\tfor_test (\"names_length retuns size of a NULL-terminated string array\") {\n     +\t\tconst char *a[] = { \"a\", \"b\", NULL };\n     +\t\tcheck_int(names_length(a), ==, 2);\n     +\t}\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tcheck(!names_equal(a, b));\n     -\tcheck(!names_equal(a, c));\n     -}\n    -+\tif (TEST_RUN(\"names_equal compares NULL-terminated string arrays\")) {\n    ++\tfor_test (\"names_equal compares NULL-terminated string arrays\") {\n     +\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n     +\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n     +\t\tconst char *c[] = { \"a\", \"b\", NULL };\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tcheck(!out[2]);\n     -\tfree_names(out);\n     -}\n    -+\tif (TEST_RUN(\"parse_names works for basic input\")) {\n    ++\tfor_test (\"parse_names works for basic input\") {\n     +\t\tchar in1[] = \"line\\n\";\n     +\t\tchar in2[] = \"a\\nb\\nc\";\n     +\t\tchar **out = NULL;\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n     -\t\tstrbuf_reset(&a);\n     -\t\tstrbuf_reset(&b);\n    -+\tif (TEST_RUN(\"parse_names drops empty string\")) {\n    ++\tfor_test (\"parse_names drops empty string\") {\n     +\t\tchar in[] = \"a\\n\\nb\\n\";\n     +\t\tchar **out = NULL;\n     +\t\tparse_names(in, strlen(in), &out);\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tout = get_be24(dest);\n     -\tcheck_int(in, ==, out);\n     -}\n    -+\tif (TEST_RUN(\"common_prefix_size works\")) {\n    ++\tfor_test (\"common_prefix_size works\") {\n     +\t\tstruct strbuf a = STRBUF_INIT;\n     +\t\tstruct strbuf b = STRBUF_INIT;\n     +\t\tstruct {\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tTEST(test_names_equal(), \"names_equal compares NULL-terminated string arrays\");\n     -\tTEST(test_u24_roundtrip(), \"put_be24 and get_be24 work\");\n     -\tTEST(test_u16_roundtrip(), \"put_be16 and get_be16 work\");\n    -+\tif (TEST_RUN(\"put_be24 and get_be24 work\")) {\n    ++\tfor_test (\"put_be24 and get_be24 work\") {\n     +\t\tuint32_t in = 0x112233;\n     +\t\tuint8_t dest[3];\n     +\t\tuint32_t out;\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     +\t\tcheck_int(in, ==, out);\n     +\t}\n     +\n    -+\tif (TEST_RUN(\"put_be16 and get_be16 work\")) {\n    ++\tfor_test (\"put_be16 and get_be16 work\") {\n     +\t\tuint32_t in = 0xfef1;\n     +\t\tuint8_t dest[3];\n     +\t\tuint32_t out;\n5:  5805a9cbd7 ! 5:  11c1675a13 t-strvec: use TEST_RUN\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-strvec: use TEST_RUN\n    +    t-strvec: use for_test\n\n         The macro TEST takes a single expression.  If a test requires multiple\n         statements then they need to be placed in a function that's called in\n         the TEST expression.\n\n    -    Remove the overhead of defining and calling single-use functions by\n    -    using TEST_RUN instead.\n    +    Remove the cognitive overhead of defining and calling single-use\n    +    functions by using for_test instead.\n\n      ## t/unit-tests/t-strvec.c ##\n     @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_uint(vec.nr, ==, 0);\n     -\tcheck_uint(vec.alloc, ==, 0);\n     -}\n    -+\tif (TEST_RUN(\"static initialization\")) {\n    ++\tfor_test (\"static initialization\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n     +\t\tcheck_uint(vec.nr, ==, 0);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_uint(vec.nr, ==, 0);\n     -\tcheck_uint(vec.alloc, ==, 0);\n     -}\n    -+\tif (TEST_RUN(\"dynamic initialization\")) {\n    ++\tfor_test (\"dynamic initialization\") {\n     +\t\tstruct strvec vec;\n     +\t\tstrvec_init(&vec);\n     +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_uint(vec.nr, ==, 0);\n     -\tcheck_uint(vec.alloc, ==, 0);\n     -}\n    -+\tif (TEST_RUN(\"clear\")) {\n    ++\tfor_test (\"clear\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_push(&vec, \"foo\");\n     +\t\tstrvec_clear(&vec);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -static void t_push(void)\n     -{\n     -\tstruct strvec vec = STRVEC_INIT;\n    -+\tif (TEST_RUN(\"push\")) {\n    ++\tfor_test (\"push\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n\n     -\tstrvec_push(&vec, \"foo\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo: 1\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"pushf\")) {\n    ++\tfor_test (\"pushf\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n     +\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"pushl\")) {\n    ++\tfor_test (\"pushl\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\t\t\"foo\", \"bar\", \"baz\", NULL,\n     -\t};\n     -\tstruct strvec vec = STRVEC_INIT;\n    -+\tif (TEST_RUN(\"pushv\")) {\n    ++\tfor_test (\"pushv\") {\n     +\t\tconst char *strings[] = {\n     +\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n     +\t\t};\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"replace at head\")) {\n    ++\tfor_test (\"replace at head\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_replace(&vec, 0, \"replaced\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"replace at tail\")) {\n    ++\tfor_test (\"replace at tail\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_replace(&vec, 2, \"replaced\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"replace in between\")) {\n    ++\tfor_test (\"replace in between\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_replace(&vec, 1, \"replaced\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"oo\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"replace with substring\")) {\n    ++\tfor_test (\"replace with substring\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", NULL);\n     +\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"remove at head\")) {\n    ++\tfor_test (\"remove at head\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_remove(&vec, 0);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"remove at tail\")) {\n    ++\tfor_test (\"remove at tail\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_remove(&vec, 2);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"remove in between\")) {\n    ++\tfor_test (\"remove in between\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_remove(&vec, 1);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"pop with empty array\")) {\n    ++\tfor_test (\"pop with empty array\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pop(&vec);\n     +\t\tcheck_strvec(&vec, NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"pop with non-empty array\")) {\n    ++\tfor_test (\"pop with non-empty array\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_pop(&vec);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"split empty string\")) {\n    ++\tfor_test (\"split empty string\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"\");\n     +\t\tcheck_strvec(&vec, NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"split single item\")) {\n    ++\tfor_test (\"split single item\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"foo\");\n     +\t\tcheck_strvec(&vec, \"foo\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"split multiple items\")) {\n    ++\tfor_test (\"split multiple items\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"foo bar baz\");\n     +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"split whitespace only\")) {\n    ++\tfor_test (\"split whitespace only\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \" \\t\\n\");\n     +\t\tcheck_strvec(&vec, NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tif (TEST_RUN(\"split multiple consecutive whitespaces\")) {\n    ++\tfor_test (\"split multiple consecutive whitespaces\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n     +\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -{\n     -\tstruct strvec vec = STRVEC_INIT;\n     -\tconst char **detached;\n    -+\tif (TEST_RUN(\"detach\")) {\n    ++\tfor_test (\"detach\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tconst char **detached;\n\n6:  188b31884b ! 6:  cd79132f95 t-strbuf: use TEST_RUN\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-strbuf: use TEST_RUN\n    +    t-strbuf: use for_test\n\n         The macro TEST takes a single expression.  If a test requires multiple\n         statements then they need to be placed in a function that's called in\n    @@ Commit message\n         are used for that purpose and take another function as an argument,\n         making the control flow hard to follow.\n\n    -    Remove the overhead of these functions by using TEST_RUN instead.  Move\n    +    Remove the overhead of these functions by using for_test instead.  Move\n         their duplicate post-condition checks into a new helper, t_release(),\n         and let t_addch() and t_addstr() accept properly typed input parameters\n         instead of void pointers.\n    @@ t/unit-tests/t-strbuf.c\n      static int assert_sane_strbuf(struct strbuf *buf)\n      {\n      \t/* Initialized strbufs should always have a non-NULL buffer */\n    -@@ t/unit-tests/t-strbuf.c: static void t_dynamic_init(void)\n    - \tstrbuf_release(&buf);\n    +@@ t/unit-tests/t-strbuf.c: static int assert_sane_strbuf(struct strbuf *buf)\n    + \treturn check_uint(buf->len, <, buf->alloc);\n      }\n\n    --static void t_addch(struct strbuf *buf, const void *data)\n    +-static void t_static_init(void)\n     +static void t_addch(struct strbuf *buf, int ch)\n      {\n    +-\tstruct strbuf buf = STRBUF_INIT;\n    +-\n    +-\tcheck_uint(buf.len, ==, 0);\n    +-\tcheck_uint(buf.alloc, ==, 0);\n    +-\tcheck_char(buf.buf[0], ==, '\\0');\n    +-}\n    +-\n    +-static void t_dynamic_init(void)\n    +-{\n    +-\tstruct strbuf buf;\n    +-\n    +-\tstrbuf_init(&buf, 1024);\n    +-\tcheck(assert_sane_strbuf(&buf));\n    +-\tcheck_uint(buf.len, ==, 0);\n    +-\tcheck_uint(buf.alloc, >=, 1024);\n    +-\tcheck_char(buf.buf[0], ==, '\\0');\n    +-\tstrbuf_release(&buf);\n    +-}\n    +-\n    +-static void t_addch(struct strbuf *buf, const void *data)\n    +-{\n     -\tconst char *p_ch = data;\n     -\tconst char ch = *p_ch;\n      \tsize_t orig_alloc = buf->alloc;\n    @@ t/unit-tests/t-strbuf.c: static void t_addstr(struct strbuf *buf, const void *da\n     +\n      int cmd_main(int argc, const char **argv)\n      {\n    - \tif (!TEST(t_static_init(), \"static initialization works\"))\n    - \t\ttest_skip_all(\"STRBUF_INIT is broken\");\n    - \tTEST(t_dynamic_init(), \"dynamic initialization works\");\n    +-\tif (!TEST(t_static_init(), \"static initialization works\"))\n    +-\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n    +-\tTEST(t_dynamic_init(), \"dynamic initialization works\");\n     -\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n     -\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n     -\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n    @@ t/unit-tests/t-strbuf.c: static void t_addstr(struct strbuf *buf, const void *da\n     -\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n     -\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n     -\t     \"strbuf_addstr appends string to initial value\");\n    ++\tfor_test (\"static initialization works\") {\n    ++\t\tstruct strbuf buf = STRBUF_INIT;\n    ++\n    ++\t\tif (!check_uint(buf.len, ==, 0) ||\n    ++\t\t    !check_uint(buf.alloc, ==, 0) ||\n    ++\t\t    !check_char(buf.buf[0], ==, '\\0'))\n    ++\t\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n    ++\t}\n    ++\n    ++\tfor_test (\"dynamic initialization works\") {\n    ++\t\tstruct strbuf buf;\n    ++\n    ++\t\tstrbuf_init(&buf, 1024);\n    ++\t\tcheck(assert_sane_strbuf(&buf));\n    ++\t\tcheck_uint(buf.len, ==, 0);\n    ++\t\tcheck_uint(buf.alloc, >=, 1024);\n    ++\t\tcheck_char(buf.buf[0], ==, '\\0');\n    ++\t\tstrbuf_release(&buf);\n    ++\t}\n     +\n    -+\tif (TEST_RUN(\"strbuf_addch adds char\")) {\n    ++\tfor_test (\"strbuf_addch adds char\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n     +\t\tt_addch(&sb, 'a');\n     +\t\tt_release(&sb);\n     +\t}\n     +\n    -+\tif (TEST_RUN(\"strbuf_addch adds NUL char\")) {\n    ++\tfor_test (\"strbuf_addch adds NUL char\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n     +\t\tt_addch(&sb, '\\0');\n     +\t\tt_release(&sb);\n     +\t}\n     +\n    -+\tif (TEST_RUN(\"strbuf_addch appends to initial value\")) {\n    ++\tfor_test (\"strbuf_addch appends to initial value\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n     +\t\tt_addstr(&sb, \"initial value\");\n     +\t\tt_addch(&sb, 'a');\n     +\t\tt_release(&sb);\n     +\t}\n     +\n    -+\tif (TEST_RUN(\"strbuf_addstr adds string\")) {\n    ++\tfor_test (\"strbuf_addstr adds string\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n     +\t\tt_addstr(&sb, \"hello there\");\n     +\t\tt_release(&sb);\n     +\t}\n     +\n    -+\tif (TEST_RUN(\"strbuf_addstr appends string to initial value\")) {\n    ++\tfor_test (\"strbuf_addstr appends string to initial value\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n     +\t\tt_addstr(&sb, \"initial value\");\n     +\t\tt_addstr(&sb, \"hello there\");\n--\n2.45.2\n"},{"id":"499024","messageId":"af5b8fef-073a-4e85-b660-9672b7d5f629@web.de","threadId":"61700","inReplyTo":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","subject":"[PATCH v2 1/6] t0080: move expected output to a file","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:15:48Z","receivedAt":"2024-07-21T06:15:58Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Provide the expected output of \"test-tool example-tap\" verbatim instead\nof as a here-doc, to avoid distractions due to quoting, variables\ncontaining quotes and indentation.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/t0080-unit-test-output.sh | 48 +------------------------------------\n t/t0080/expect              | 43 +++++++++++++++++++++++++++++++++\n 2 files changed, 44 insertions(+), 47 deletions(-)\n create mode 100644 t/t0080/expect\n\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex 7bbb065d58..93f2defa19 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -6,54 +6,8 @@ TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n\n test_expect_success 'TAP output from unit tests' '\n-\tcat >expect <<-EOF &&\n-\tok 1 - passing test\n-\tok 2 - passing test and assertion return 1\n-\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n-\t#    left: 1\n-\t#   right: 2\n-\tnot ok 3 - failing test\n-\tok 4 - failing test and assertion return 0\n-\tnot ok 5 - passing TEST_TODO() # TODO\n-\tok 6 - passing TEST_TODO() returns 1\n-\t# todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26\n-\tnot ok 7 - failing TEST_TODO()\n-\tok 8 - failing TEST_TODO() returns 0\n-\t# check \"0\" failed at t/helper/test-example-tap.c:31\n-\t# skipping test - missing prerequisite\n-\t# skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33\n-\tok 9 - test_skip() # SKIP\n-\tok 10 - skipped test returns 1\n-\t# skipping test - missing prerequisite\n-\tok 11 - test_skip() inside TEST_TODO() # SKIP\n-\tok 12 - test_skip() inside TEST_TODO() returns 1\n-\t# check \"0\" failed at t/helper/test-example-tap.c:49\n-\tnot ok 13 - TEST_TODO() after failing check\n-\tok 14 - TEST_TODO() after failing check returns 0\n-\t# check \"0\" failed at t/helper/test-example-tap.c:57\n-\tnot ok 15 - failing check after TEST_TODO()\n-\tok 16 - failing check after TEST_TODO() returns 0\n-\t# check \"!strcmp(\"\\thello\\\\\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:62\n-\t#    left: \"\\011hello\\\\\\\\\"\n-\t#   right: \"there\\\"\\012\"\n-\t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:63\n-\t#    left: \"NULL\"\n-\t#   right: NULL\n-\t# check \"${SQ}a${SQ} == ${SQ}\\n${SQ}\" failed at t/helper/test-example-tap.c:64\n-\t#    left: ${SQ}a${SQ}\n-\t#   right: ${SQ}\\012${SQ}\n-\t# check \"${SQ}\\\\\\\\${SQ} == ${SQ}\\\\${SQ}${SQ}\" failed at t/helper/test-example-tap.c:65\n-\t#    left: ${SQ}\\\\\\\\${SQ}\n-\t#   right: ${SQ}\\\\${SQ}${SQ}\n-\tnot ok 17 - messages from failing string and char comparison\n-\t# BUG: test has no checks at t/helper/test-example-tap.c:92\n-\tnot ok 18 - test with no checks\n-\tok 19 - test with no checks returns 0\n-\t1..19\n-\tEOF\n-\n \t! test-tool example-tap >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp \"$TEST_DIRECTORY\"/t0080/expect actual\n '\n\n test_done\ndiff --git a/t/t0080/expect b/t/t0080/expect\nnew file mode 100644\nindex 0000000000..0cfa0dc6d8\n--- /dev/null\n+++ b/t/t0080/expect\n@@ -0,0 +1,43 @@\n+ok 1 - passing test\n+ok 2 - passing test and assertion return 1\n+# check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n+#    left: 1\n+#   right: 2\n+not ok 3 - failing test\n+ok 4 - failing test and assertion return 0\n+not ok 5 - passing TEST_TODO() # TODO\n+ok 6 - passing TEST_TODO() returns 1\n+# todo check 'check(x)' succeeded at t/helper/test-example-tap.c:26\n+not ok 7 - failing TEST_TODO()\n+ok 8 - failing TEST_TODO() returns 0\n+# check \"0\" failed at t/helper/test-example-tap.c:31\n+# skipping test - missing prerequisite\n+# skipping check '1' at t/helper/test-example-tap.c:33\n+ok 9 - test_skip() # SKIP\n+ok 10 - skipped test returns 1\n+# skipping test - missing prerequisite\n+ok 11 - test_skip() inside TEST_TODO() # SKIP\n+ok 12 - test_skip() inside TEST_TODO() returns 1\n+# check \"0\" failed at t/helper/test-example-tap.c:49\n+not ok 13 - TEST_TODO() after failing check\n+ok 14 - TEST_TODO() after failing check returns 0\n+# check \"0\" failed at t/helper/test-example-tap.c:57\n+not ok 15 - failing check after TEST_TODO()\n+ok 16 - failing check after TEST_TODO() returns 0\n+# check \"!strcmp(\"\\thello\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:62\n+#    left: \"\\011hello\\\\\"\n+#   right: \"there\\\"\\012\"\n+# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:63\n+#    left: \"NULL\"\n+#   right: NULL\n+# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n+#    left: 'a'\n+#   right: '\\012'\n+# check \"'\\\\' == '\\''\" failed at t/helper/test-example-tap.c:65\n+#    left: '\\\\'\n+#   right: '\\''\n+not ok 17 - messages from failing string and char comparison\n+# BUG: test has no checks at t/helper/test-example-tap.c:92\n+not ok 18 - test with no checks\n+ok 19 - test with no checks returns 0\n+1..19\n--\n2.45.2\n"},{"id":"499025","messageId":"2dff757d-3c5d-4923-97df-26bcb1c21230@web.de","threadId":"61700","inReplyTo":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","subject":"[PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:21:36Z","receivedAt":"2024-07-21T06:21:54Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST only allows defining a test that consists of a single\nexpression.  Add a new macro, for_test, which provides a way to define\nunit tests that are made up of one or more statements.\n\nfor_test allows defining self-contained tests en bloc, a bit like\ntest_expect_success does for regular tests.  It acts like a for loop\nthat runs at most once; the test body is executed if test_skip_all()\nhad not been called before.\n\nHelped-by: Phillip Wood <phillip.wood123@gmail.com>\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n .clang-format               |  2 ++\n t/helper/test-example-tap.c | 33 +++++++++++++++++++++++++++++++++\n t/t0080/expect              | 35 ++++++++++++++++++++++++++++++++++-\n t/unit-tests/test-lib.h     | 19 +++++++++++++++++++\n 4 files changed, 88 insertions(+), 1 deletion(-)\n\ndiff --git a/.clang-format b/.clang-format\nindex 6408251577..863dc87dfc 100644\n--- a/.clang-format\n+++ b/.clang-format\n@@ -151,6 +151,7 @@ Cpp11BracedListStyle: false\n # function calls. Taken from:\n #   git grep -h '^#define [^[:space:]]*for_\\?each[^[:space:]]*(' |\n #   sed \"s/^#define /  - '/; s/(.*$/'/\" | sort | uniq\n+# Added for_test from t/unit-tests/test-lib.h manually as a special case.\n ForEachMacros:\n   - 'for_each_builtin'\n   - 'for_each_string_list_item'\n@@ -168,6 +169,7 @@ ForEachMacros:\n   - 'strintmap_for_each_entry'\n   - 'strmap_for_each_entry'\n   - 'strset_for_each_entry'\n+  - 'for_test'\n\n # The maximum number of consecutive empty lines to keep.\n MaxEmptyLinesToKeep: 1\ndiff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\nindex d072ad559f..51d5e6e75b 100644\n--- a/t/helper/test-example-tap.c\n+++ b/t/helper/test-example-tap.c\n@@ -92,5 +92,38 @@ int cmd__example_tap(int argc, const char **argv)\n \ttest_res = TEST(t_empty(), \"test with no checks\");\n \tTEST(check_int(test_res, ==, 0), \"test with no checks returns 0\");\n\n+\tfor_test (\"for_test passing test\")\n+\t\tcheck_int(1, ==, 1);\n+\tfor_test (\"for_test failing test\")\n+\t\tcheck_int(1, ==, 2);\n+\tfor_test (\"for_test passing TEST_TODO()\")\n+\t\tTEST_TODO(check(0));\n+\tfor_test (\"for_test failing TEST_TODO()\")\n+\t\tTEST_TODO(check(1));\n+\tfor_test (\"for_test test_skip()\") {\n+\t\tcheck(0);\n+\t\ttest_skip(\"missing prerequisite\");\n+\t\tcheck(1);\n+\t}\n+\tfor_test (\"for_test test_skip() inside TEST_TODO()\")\n+\t\tTEST_TODO((test_skip(\"missing prerequisite\"), 1));\n+\tfor_test (\"for_test TEST_TODO() after failing check\") {\n+\t\tcheck(0);\n+\t\tTEST_TODO(check(0));\n+\t}\n+\tfor_test (\"for_test failing check after TEST_TODO()\") {\n+\t\tcheck(1);\n+\t\tTEST_TODO(check(0));\n+\t\tcheck(0);\n+\t}\n+\tfor_test (\"for_test messages from failing string and char comparison\") {\n+\t\tcheck_str(\"\\thello\\\\\", \"there\\\"\\n\");\n+\t\tcheck_str(\"NULL\", NULL);\n+\t\tcheck_char('a', ==, '\\n');\n+\t\tcheck_char('\\\\', ==, '\\'');\n+\t}\n+\tfor_test (\"for_test test with no checks\")\n+\t\t; /* nothing */\n+\n \treturn test_done();\n }\ndiff --git a/t/t0080/expect b/t/t0080/expect\nindex 0cfa0dc6d8..583f41b8c9 100644\n--- a/t/t0080/expect\n+++ b/t/t0080/expect\n@@ -40,4 +40,37 @@ not ok 17 - messages from failing string and char comparison\n # BUG: test has no checks at t/helper/test-example-tap.c:92\n not ok 18 - test with no checks\n ok 19 - test with no checks returns 0\n-1..19\n+ok 20 - for_test passing test\n+# check \"1 == 2\" failed at t/helper/test-example-tap.c:98\n+#    left: 1\n+#   right: 2\n+not ok 21 - for_test failing test\n+not ok 22 - for_test passing TEST_TODO() # TODO\n+# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:102\n+not ok 23 - for_test failing TEST_TODO()\n+# check \"0\" failed at t/helper/test-example-tap.c:104\n+# skipping test - missing prerequisite\n+# skipping check '1' at t/helper/test-example-tap.c:106\n+ok 24 - for_test test_skip() # SKIP\n+# skipping test - missing prerequisite\n+ok 25 - for_test test_skip() inside TEST_TODO() # SKIP\n+# check \"0\" failed at t/helper/test-example-tap.c:111\n+not ok 26 - for_test TEST_TODO() after failing check\n+# check \"0\" failed at t/helper/test-example-tap.c:117\n+not ok 27 - for_test failing check after TEST_TODO()\n+# check \"!strcmp(\"\\thello\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:120\n+#    left: \"\\011hello\\\\\"\n+#   right: \"there\\\"\\012\"\n+# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:121\n+#    left: \"NULL\"\n+#   right: NULL\n+# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:122\n+#    left: 'a'\n+#   right: '\\012'\n+# check \"'\\\\' == '\\''\" failed at t/helper/test-example-tap.c:123\n+#    left: '\\\\'\n+#   right: '\\''\n+not ok 28 - for_test messages from failing string and char comparison\n+# BUG: test has no checks at t/helper/test-example-tap.c:125\n+not ok 29 - for_test test with no checks\n+1..29\ndiff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h\nindex 2de6d715d5..12afd47ac9 100644\n--- a/t/unit-tests/test-lib.h\n+++ b/t/unit-tests/test-lib.h\n@@ -14,6 +14,25 @@\n \ttest__run_end(test__run_begin() ? 0 : (t, 1),\t\\\n \t\t      TEST_LOCATION(),  __VA_ARGS__)\n\n+/*\n+ * Run a test unless test_skip_all() has been called.  Acts like a for\n+ * loop that runs at most once, with the test description between the\n+ * parentheses and the test body as a statement or block after them.\n+ * The description for each test should be unique.  E.g.:\n+ *\n+ *  for_test (\"something else %d %d\", arg1, arg2) {\n+ *          prepare();\n+ *          test_something_else(arg1, arg2);\n+ *          cleanup();\n+ *  }\n+ */\n+#define for_test(...)\t\t\t\t\t\t\t\\\n+\tfor (int for_test_running_ = test__run_begin() ?\t\t\\\n+\t\t(test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n+\t     for_test_running_;\t\t\t\t\t\t\\\n+\t     test__run_end(1, TEST_LOCATION(), __VA_ARGS__),\t\t\\\n+\t\tfor_test_running_ = 0)\n+\n /*\n  * Print a test plan, should be called before any tests. If the number\n  * of tests is not known in advance test_done() will automatically\n--\n2.45.2\n"},{"id":"499026","messageId":"d97ae165-988a-4f33-a61c-8254fe138fbe@web.de","threadId":"61700","inReplyTo":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","subject":"[PATCH v2 3/6] t-ctype: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:22:36Z","receivedAt":"2024-07-21T06:22:47Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Use the documented macro for_test instead of the internal functions\ntest__run_begin() and test__run_end(), which are supposed to be private\nto the unit test framework.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-ctype.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c\nindex d6ac1fe678..92a05f02b3 100644\n--- a/t/unit-tests/t-ctype.c\n+++ b/t/unit-tests/t-ctype.c\n@@ -4,15 +4,13 @@\n \tsize_t len = ARRAY_SIZE(string) - 1 + \\\n \t\tBUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \\\n \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n-\tint skip = test__run_begin(); \\\n-\tif (!skip) { \\\n+\tfor_test (#class \" works\") { \\\n \t\tfor (int i = 0; i < 256; i++) { \\\n \t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n \t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n \t\t} \\\n \t\tcheck(!class(EOF)); \\\n \t} \\\n-\ttest__run_end(!skip, TEST_LOCATION(), #class \" works\"); \\\n } while (0)\n\n #define DIGIT \"0123456789\"\n--\n2.45.2\n"},{"id":"499027","messageId":"e048ef54-5824-452d-ab1f-233581711f4e@web.de","threadId":"61700","inReplyTo":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","subject":"[PATCH v2 4/6] t-reftable-basics: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:23:59Z","receivedAt":"2024-07-21T06:24:15Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the overhead of defining and calling single-use functions by\nusing for_test instead.\n\nRun the tests in the order of definition.  We can reorder them like that\nbecause they are independent.  Technically this changes the output, but\nretains the meaning of a full run and allows for easier review e.g. with\ndiff option --ignore-all-space.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-reftable-basics.c | 228 ++++++++++++++-----------------\n 1 file changed, 106 insertions(+), 122 deletions(-)\n\ndiff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c\nindex 4e80bdf16d..eb7af9ade4 100644\n--- a/t/unit-tests/t-reftable-basics.c\n+++ b/t/unit-tests/t-reftable-basics.c\n@@ -20,141 +20,125 @@ static int integer_needle_lesseq(size_t i, void *_args)\n \treturn args->needle <= args->haystack[i];\n }\n\n-static void test_binsearch(void)\n+int cmd_main(int argc, const char *argv[])\n {\n-\tint haystack[] = { 2, 4, 6, 8, 10 };\n-\tstruct {\n-\t\tint needle;\n-\t\tsize_t expected_idx;\n-\t} testcases[] = {\n-\t\t{-9000, 0},\n-\t\t{-1, 0},\n-\t\t{0, 0},\n-\t\t{2, 0},\n-\t\t{3, 1},\n-\t\t{4, 1},\n-\t\t{7, 3},\n-\t\t{9, 4},\n-\t\t{10, 4},\n-\t\t{11, 5},\n-\t\t{9000, 5},\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n-\t\tstruct integer_needle_lesseq_args args = {\n-\t\t\t.haystack = haystack,\n-\t\t\t.needle = testcases[i].needle,\n+\tfor_test (\"binary search with binsearch works\") {\n+\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n+\t\tstruct {\n+\t\t\tint needle;\n+\t\t\tsize_t expected_idx;\n+\t\t} testcases[] = {\n+\t\t\t{-9000, 0},\n+\t\t\t{-1, 0},\n+\t\t\t{0, 0},\n+\t\t\t{2, 0},\n+\t\t\t{3, 1},\n+\t\t\t{4, 1},\n+\t\t\t{7, 3},\n+\t\t\t{9, 4},\n+\t\t\t{10, 4},\n+\t\t\t{11, 5},\n+\t\t\t{9000, 5},\n \t\t};\n-\t\tsize_t idx;\n\n-\t\tidx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args);\n-\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n+\t\t\tstruct integer_needle_lesseq_args args = {\n+\t\t\t\t.haystack = haystack,\n+\t\t\t\t.needle = testcases[i].needle,\n+\t\t\t};\n+\t\t\tsize_t idx;\n+\n+\t\t\tidx = binsearch(ARRAY_SIZE(haystack),\n+\t\t\t\t\t&integer_needle_lesseq, &args);\n+\t\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\t}\n \t}\n-}\n\n-static void test_names_length(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", NULL };\n-\tcheck_int(names_length(a), ==, 2);\n-}\n-\n-static void test_names_equal(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tconst char *c[] = { \"a\", \"b\", NULL };\n+\tfor_test (\"names_length retuns size of a NULL-terminated string array\") {\n+\t\tconst char *a[] = { \"a\", \"b\", NULL };\n+\t\tcheck_int(names_length(a), ==, 2);\n+\t}\n\n-\tcheck(names_equal(a, a));\n-\tcheck(!names_equal(a, b));\n-\tcheck(!names_equal(a, c));\n-}\n+\tfor_test (\"names_equal compares NULL-terminated string arrays\") {\n+\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\t\tconst char *c[] = { \"a\", \"b\", NULL };\n\n-static void test_parse_names_normal(void)\n-{\n-\tchar in1[] = \"line\\n\";\n-\tchar in2[] = \"a\\nb\\nc\";\n-\tchar **out = NULL;\n-\tparse_names(in1, strlen(in1), &out);\n-\tcheck_str(out[0], \"line\");\n-\tcheck(!out[1]);\n-\tfree_names(out);\n-\n-\tparse_names(in2, strlen(in2), &out);\n-\tcheck_str(out[0], \"a\");\n-\tcheck_str(out[1], \"b\");\n-\tcheck_str(out[2], \"c\");\n-\tcheck(!out[3]);\n-\tfree_names(out);\n-}\n+\t\tcheck(names_equal(a, a));\n+\t\tcheck(!names_equal(a, b));\n+\t\tcheck(!names_equal(a, c));\n+\t}\n\n-static void test_parse_names_drop_empty(void)\n-{\n-\tchar in[] = \"a\\n\\nb\\n\";\n-\tchar **out = NULL;\n-\tparse_names(in, strlen(in), &out);\n-\tcheck_str(out[0], \"a\");\n-\t/* simply '\\n' should be dropped as empty string */\n-\tcheck_str(out[1], \"b\");\n-\tcheck(!out[2]);\n-\tfree_names(out);\n-}\n+\tfor_test (\"parse_names works for basic input\") {\n+\t\tchar in1[] = \"line\\n\";\n+\t\tchar in2[] = \"a\\nb\\nc\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in1, strlen(in1), &out);\n+\t\tcheck_str(out[0], \"line\");\n+\t\tcheck(!out[1]);\n+\t\tfree_names(out);\n+\n+\t\tparse_names(in2, strlen(in2), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck_str(out[2], \"c\");\n+\t\tcheck(!out[3]);\n+\t\tfree_names(out);\n+\t}\n\n-static void test_common_prefix(void)\n-{\n-\tstruct strbuf a = STRBUF_INIT;\n-\tstruct strbuf b = STRBUF_INIT;\n-\tstruct {\n-\t\tconst char *a, *b;\n-\t\tint want;\n-\t} cases[] = {\n-\t\t{\"abcdef\", \"abc\", 3},\n-\t\t{ \"abc\", \"ab\", 2 },\n-\t\t{ \"\", \"abc\", 0 },\n-\t\t{ \"abc\", \"abd\", 2 },\n-\t\t{ \"abc\", \"pqr\", 0 },\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n-\t\tstrbuf_addstr(&a, cases[i].a);\n-\t\tstrbuf_addstr(&b, cases[i].b);\n-\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n-\t\tstrbuf_reset(&a);\n-\t\tstrbuf_reset(&b);\n+\tfor_test (\"parse_names drops empty string\") {\n+\t\tchar in[] = \"a\\n\\nb\\n\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in, strlen(in), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\t/* simply '\\n' should be dropped as empty string */\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck(!out[2]);\n+\t\tfree_names(out);\n \t}\n-\tstrbuf_release(&a);\n-\tstrbuf_release(&b);\n-}\n\n-static void test_u24_roundtrip(void)\n-{\n-\tuint32_t in = 0x112233;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be24(dest, in);\n-\tout = get_be24(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\tfor_test (\"common_prefix_size works\") {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstruct {\n+\t\t\tconst char *a, *b;\n+\t\t\tint want;\n+\t\t} cases[] = {\n+\t\t\t{\"abcdef\", \"abc\", 3},\n+\t\t\t{ \"abc\", \"ab\", 2 },\n+\t\t\t{ \"\", \"abc\", 0 },\n+\t\t\t{ \"abc\", \"abd\", 2 },\n+\t\t\t{ \"abc\", \"pqr\", 0 },\n+\t\t};\n\n-static void test_u16_roundtrip(void)\n-{\n-\tuint32_t in = 0xfef1;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be16(dest, in);\n-\tout = get_be16(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n+\t\t\tstrbuf_reset(&a);\n+\t\t\tstrbuf_reset(&b);\n+\t\t}\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n\n-int cmd_main(int argc, const char *argv[])\n-{\n-\tTEST(test_common_prefix(), \"common_prefix_size works\");\n-\tTEST(test_parse_names_normal(), \"parse_names works for basic input\");\n-\tTEST(test_parse_names_drop_empty(), \"parse_names drops empty string\");\n-\tTEST(test_binsearch(), \"binary search with binsearch works\");\n-\tTEST(test_names_length(), \"names_length retuns size of a NULL-terminated string array\");\n-\tTEST(test_names_equal(), \"names_equal compares NULL-terminated string arrays\");\n-\tTEST(test_u24_roundtrip(), \"put_be24 and get_be24 work\");\n-\tTEST(test_u16_roundtrip(), \"put_be16 and get_be16 work\");\n+\tfor_test (\"put_be24 and get_be24 work\") {\n+\t\tuint32_t in = 0x112233;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be24(dest, in);\n+\t\tout = get_be24(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n+\n+\tfor_test (\"put_be16 and get_be16 work\") {\n+\t\tuint32_t in = 0xfef1;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be16(dest, in);\n+\t\tout = get_be16(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"499028","messageId":"d64b0668-dbd2-411b-bc5c-19667a01b7d7@web.de","threadId":"61700","inReplyTo":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","subject":"[PATCH v2 5/6] t-strvec: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:24:50Z","receivedAt":"2024-07-21T06:25:00Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the cognitive overhead of defining and calling single-use\nfunctions by using for_test instead.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strvec.c | 356 ++++++++++++++++++----------------------\n 1 file changed, 156 insertions(+), 200 deletions(-)\n\ndiff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c\nindex d4615ab06d..4fa2c21afb 100644\n--- a/t/unit-tests/t-strvec.c\n+++ b/t/unit-tests/t-strvec.c\n@@ -36,237 +36,193 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n \tcheck_pointer_eq(vec->v[nr], NULL);\n }\n\n-static void t_static_init(void)\n+int cmd_main(int argc, const char **argv)\n {\n-\tstruct strvec vec = STRVEC_INIT;\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tfor_test (\"static initialization\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_dynamic_init(void)\n-{\n-\tstruct strvec vec;\n-\tstrvec_init(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tfor_test (\"dynamic initialization\") {\n+\t\tstruct strvec vec;\n+\t\tstrvec_init(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_clear(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_push(&vec, \"foo\");\n-\tstrvec_clear(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tfor_test (\"clear\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_clear(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_push(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n+\tfor_test (\"push\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_push(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n\n-\tstrvec_push(&vec, \"bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_push(&vec, \"bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushf(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushf(&vec, \"foo: %d\", 1);\n-\tcheck_strvec(&vec, \"foo: 1\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pushf\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n+\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushl(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pushl\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushv(void)\n-{\n-\tconst char *strings[] = {\n-\t\t\"foo\", \"bar\", \"baz\", NULL,\n-\t};\n-\tstruct strvec vec = STRVEC_INIT;\n+\tfor_test (\"pushv\") {\n+\t\tconst char *strings[] = {\n+\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n+\t\t};\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_pushv(&vec, strings);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pushv(&vec, strings);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 0, \"replaced\");\n-\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace at head\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 0, \"replaced\");\n+\t\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 2, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace at tail\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 2, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 1, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace in between\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 1, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_with_substring(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", NULL);\n-\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n-\tcheck_strvec(&vec, \"oo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace with substring\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", NULL);\n+\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n+\t\tcheck_strvec(&vec, \"oo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 0);\n-\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"remove at head\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 0);\n+\t\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 2);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"remove at tail\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 2);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 1);\n-\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"remove in between\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 1);\n+\t\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pop with empty array\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_non_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pop with non-empty array\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_empty_string(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split empty string\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_single_item(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split single item\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_items(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo bar baz\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split multiple items\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo bar baz\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_whitespace_only(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \" \\t\\n\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split whitespace only\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \" \\t\\n\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_consecutive_whitespaces(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\\n\\t bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split multiple consecutive whitespaces\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_detach(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tconst char **detached;\n+\tfor_test (\"detach\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tconst char **detached;\n\n-\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_push(&vec, \"foo\");\n\n-\tdetached = strvec_detach(&vec);\n-\tcheck_str(detached[0], \"foo\");\n-\tcheck_pointer_eq(detached[1], NULL);\n+\t\tdetached = strvec_detach(&vec);\n+\t\tcheck_str(detached[0], \"foo\");\n+\t\tcheck_pointer_eq(detached[1], NULL);\n\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n\n-\tfree((char *) detached[0]);\n-\tfree(detached);\n-}\n+\t\tfree((char *) detached[0]);\n+\t\tfree(detached);\n+\t}\n\n-int cmd_main(int argc, const char **argv)\n-{\n-\tTEST(t_static_init(), \"static initialization\");\n-\tTEST(t_dynamic_init(), \"dynamic initialization\");\n-\tTEST(t_clear(), \"clear\");\n-\tTEST(t_push(), \"push\");\n-\tTEST(t_pushf(), \"pushf\");\n-\tTEST(t_pushl(), \"pushl\");\n-\tTEST(t_pushv(), \"pushv\");\n-\tTEST(t_replace_at_head(), \"replace at head\");\n-\tTEST(t_replace_in_between(), \"replace in between\");\n-\tTEST(t_replace_at_tail(), \"replace at tail\");\n-\tTEST(t_replace_with_substring(), \"replace with substring\");\n-\tTEST(t_remove_at_head(), \"remove at head\");\n-\tTEST(t_remove_in_between(), \"remove in between\");\n-\tTEST(t_remove_at_tail(), \"remove at tail\");\n-\tTEST(t_pop_empty_array(), \"pop with empty array\");\n-\tTEST(t_pop_non_empty_array(), \"pop with non-empty array\");\n-\tTEST(t_split_empty_string(), \"split empty string\");\n-\tTEST(t_split_single_item(), \"split single item\");\n-\tTEST(t_split_multiple_items(), \"split multiple items\");\n-\tTEST(t_split_whitespace_only(), \"split whitespace only\");\n-\tTEST(t_split_multiple_consecutive_whitespaces(), \"split multiple consecutive whitespaces\");\n-\tTEST(t_detach(), \"detach\");\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"499029","messageId":"b179d004-d00b-4ed7-8cc9-f3b388f101f4@web.de","threadId":"61700","inReplyTo":"da7ed537-1c8e-42ec-aa91-49e1319e8c68@web.de","subject":"[PATCH v2 6/6] t-strbuf: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-21T06:26:16Z","receivedAt":"2024-07-21T06:26:26Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.  The functions setup() and setup_populated() here\nare used for that purpose and take another function as an argument,\nmaking the control flow hard to follow.\n\nRemove the overhead of these functions by using for_test instead.  Move\ntheir duplicate post-condition checks into a new helper, t_release(),\nand let t_addch() and t_addstr() accept properly typed input parameters\ninstead of void pointers.\n\nUse the fully checking t_addstr() for adding initial values instead of\nonly doing only a length comparison -- there's no need for skipping the\nother checks.\n\nThis results in test cases that look much more like strbuf usage in\nproduction code, only with checked strbuf functions replaced by checking\nwrappers.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strbuf.c | 122 ++++++++++++++++++++--------------------\n 1 file changed, 60 insertions(+), 62 deletions(-)\n\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex 6027dafef7..82cae4cbe3 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -1,32 +1,6 @@\n #include \"test-lib.h\"\n #include \"strbuf.h\"\n\n-/* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, const void*),\n-\t\t  const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n-/* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, const void*),\n-\t\t\t    const char *init_str, const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tstrbuf_addstr(&buf, init_str);\n-\tcheck_uint(buf.len, ==, strlen(init_str));\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n static int assert_sane_strbuf(struct strbuf *buf)\n {\n \t/* Initialized strbufs should always have a non-NULL buffer */\n@@ -45,31 +19,8 @@ static int assert_sane_strbuf(struct strbuf *buf)\n \treturn check_uint(buf->len, <, buf->alloc);\n }\n\n-static void t_static_init(void)\n+static void t_addch(struct strbuf *buf, int ch)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-\tcheck_char(buf.buf[0], ==, '\\0');\n-}\n-\n-static void t_dynamic_init(void)\n-{\n-\tstruct strbuf buf;\n-\n-\tstrbuf_init(&buf, 1024);\n-\tcheck(assert_sane_strbuf(&buf));\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, >=, 1024);\n-\tcheck_char(buf.buf[0], ==, '\\0');\n-\tstrbuf_release(&buf);\n-}\n-\n-static void t_addch(struct strbuf *buf, const void *data)\n-{\n-\tconst char *p_ch = data;\n-\tconst char ch = *p_ch;\n \tsize_t orig_alloc = buf->alloc;\n \tsize_t orig_len = buf->len;\n\n@@ -85,9 +36,8 @@ static void t_addch(struct strbuf *buf, const void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n\n-static void t_addstr(struct strbuf *buf, const void *data)\n+static void t_addstr(struct strbuf *buf, const char *text)\n {\n-\tconst char *text = data;\n \tsize_t len = strlen(text);\n \tsize_t orig_alloc = buf->alloc;\n \tsize_t orig_len = buf->len;\n@@ -105,18 +55,66 @@ static void t_addstr(struct strbuf *buf, const void *data)\n \tcheck_str(buf->buf + orig_len, text);\n }\n\n+static void t_release(struct strbuf *sb)\n+{\n+\tstrbuf_release(sb);\n+\tcheck_uint(sb->len, ==, 0);\n+\tcheck_uint(sb->alloc, ==, 0);\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n-\tif (!TEST(t_static_init(), \"static initialization works\"))\n-\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n-\tTEST(t_dynamic_init(), \"dynamic initialization works\");\n-\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n-\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n-\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n-\t     \"strbuf_addch appends to initial value\");\n-\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n-\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n-\t     \"strbuf_addstr appends string to initial value\");\n+\tfor_test (\"static initialization works\") {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tif (!check_uint(buf.len, ==, 0) ||\n+\t\t    !check_uint(buf.alloc, ==, 0) ||\n+\t\t    !check_char(buf.buf[0], ==, '\\0'))\n+\t\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n+\t}\n+\n+\tfor_test (\"dynamic initialization works\") {\n+\t\tstruct strbuf buf;\n+\n+\t\tstrbuf_init(&buf, 1024);\n+\t\tcheck(assert_sane_strbuf(&buf));\n+\t\tcheck_uint(buf.len, ==, 0);\n+\t\tcheck_uint(buf.alloc, >=, 1024);\n+\t\tcheck_char(buf.buf[0], ==, '\\0');\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tfor_test (\"strbuf_addch adds char\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addch(&sb, 'a');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addch adds NUL char\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addch(&sb, '\\0');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addch appends to initial value\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addstr(&sb, \"initial value\");\n+\t\tt_addch(&sb, 'a');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addstr adds string\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addstr(&sb, \"hello there\");\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addstr appends string to initial value\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tt_addstr(&sb, \"initial value\");\n+\t\tt_addstr(&sb, \"hello there\");\n+\t\tt_release(&sb);\n+\t}\n\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"499076","messageId":"CAO_smVhoobWpsbYHnHJqTj7TJJ1udo_UaGdbOnUqe5jzL+tyaQ@mail.gmail.com","threadId":"61700","inReplyTo":"2dff757d-3c5d-4923-97df-26bcb1c21230@web.de","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-22T19:13:00Z","receivedAt":"2024-07-22T19:13:19Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Sat, Jul 20, 2024 at 11:22 PM René Scharfe <l.s.r@web.de> wrote:\n>\n> The macro TEST only allows defining a test that consists of a single\n> expression.  Add a new macro, for_test, which provides a way to define\n> unit tests that are made up of one or more statements.\n>\n> for_test allows defining self-contained tests en bloc, a bit like\n> test_expect_success does for regular tests.  It acts like a for loop\n> that runs at most once; the test body is executed if test_skip_all()\n> had not been called before.\n\nI can see based on this description where the name came from, but\nwithout this context, it's not clear when reading a test what it\nactually does. The name comes from an implementation detail, and is\nnot describing what it _does_, just _how_ it does it.\n\nMaybe a name like `small_test` or `quick_test` or something like that\nwould better express the intended usage?\n\n>\n> Helped-by: Phillip Wood <phillip.wood123@gmail.com>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  .clang-format               |  2 ++\n>  t/helper/test-example-tap.c | 33 +++++++++++++++++++++++++++++++++\n>  t/t0080/expect              | 35 ++++++++++++++++++++++++++++++++++-\n>  t/unit-tests/test-lib.h     | 19 +++++++++++++++++++\n>  4 files changed, 88 insertions(+), 1 deletion(-)\n>\n> diff --git a/.clang-format b/.clang-format\n> index 6408251577..863dc87dfc 100644\n> --- a/.clang-format\n> +++ b/.clang-format\n> @@ -151,6 +151,7 @@ Cpp11BracedListStyle: false\n>  # function calls. Taken from:\n>  #   git grep -h '^#define [^[:space:]]*for_\\?each[^[:space:]]*(' |\n>  #   sed \"s/^#define /  - '/; s/(.*$/'/\" | sort | uniq\n> +# Added for_test from t/unit-tests/test-lib.h manually as a special case.\n>  ForEachMacros:\n>    - 'for_each_builtin'\n>    - 'for_each_string_list_item'\n> @@ -168,6 +169,7 @@ ForEachMacros:\n>    - 'strintmap_for_each_entry'\n>    - 'strmap_for_each_entry'\n>    - 'strset_for_each_entry'\n> +  - 'for_test'\n>\n>  # The maximum number of consecutive empty lines to keep.\n>  MaxEmptyLinesToKeep: 1\n> diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\n> index d072ad559f..51d5e6e75b 100644\n> --- a/t/helper/test-example-tap.c\n> +++ b/t/helper/test-example-tap.c\n> @@ -92,5 +92,38 @@ int cmd__example_tap(int argc, const char **argv)\n>         test_res = TEST(t_empty(), \"test with no checks\");\n>         TEST(check_int(test_res, ==, 0), \"test with no checks returns 0\");\n>\n> +       for_test (\"for_test passing test\")\n> +               check_int(1, ==, 1);\n\nI'm concerned that users will write this like:\n+       for_test (\"for_test passing test\");\n+               check_int(1, ==, 1);\n\nAnd the issue won't be caught. Maybe that's fine; the only issue here\nis that it's not going to be as descriptive if it fails, right?\n\n> +       for_test (\"for_test failing test\")\n> +               check_int(1, ==, 2);\n> +       for_test (\"for_test passing TEST_TODO()\")\n> +               TEST_TODO(check(0));\n> +       for_test (\"for_test failing TEST_TODO()\")\n> +               TEST_TODO(check(1));\n> +       for_test (\"for_test test_skip()\") {\n> +               check(0);\n> +               test_skip(\"missing prerequisite\");\n> +               check(1);\n> +       }\n> +       for_test (\"for_test test_skip() inside TEST_TODO()\")\n> +               TEST_TODO((test_skip(\"missing prerequisite\"), 1));\n> +       for_test (\"for_test TEST_TODO() after failing check\") {\n> +               check(0);\n> +               TEST_TODO(check(0));\n> +       }\n> +       for_test (\"for_test failing check after TEST_TODO()\") {\n> +               check(1);\n> +               TEST_TODO(check(0));\n> +               check(0);\n> +       }\n> +       for_test (\"for_test messages from failing string and char comparison\") {\n> +               check_str(\"\\thello\\\\\", \"there\\\"\\n\");\n> +               check_str(\"NULL\", NULL);\n> +               check_char('a', ==, '\\n');\n> +               check_char('\\\\', ==, '\\'');\n> +       }\n> +       for_test (\"for_test test with no checks\")\n> +               ; /* nothing */\n> +\n>         return test_done();\n>  }\n> diff --git a/t/t0080/expect b/t/t0080/expect\n> index 0cfa0dc6d8..583f41b8c9 100644\n> --- a/t/t0080/expect\n> +++ b/t/t0080/expect\n> @@ -40,4 +40,37 @@ not ok 17 - messages from failing string and char comparison\n>  # BUG: test has no checks at t/helper/test-example-tap.c:92\n>  not ok 18 - test with no checks\n>  ok 19 - test with no checks returns 0\n> -1..19\n> +ok 20 - for_test passing test\n> +# check \"1 == 2\" failed at t/helper/test-example-tap.c:98\n> +#    left: 1\n> +#   right: 2\n> +not ok 21 - for_test failing test\n> +not ok 22 - for_test passing TEST_TODO() # TODO\n> +# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:102\n> +not ok 23 - for_test failing TEST_TODO()\n> +# check \"0\" failed at t/helper/test-example-tap.c:104\n> +# skipping test - missing prerequisite\n> +# skipping check '1' at t/helper/test-example-tap.c:106\n> +ok 24 - for_test test_skip() # SKIP\n> +# skipping test - missing prerequisite\n> +ok 25 - for_test test_skip() inside TEST_TODO() # SKIP\n> +# check \"0\" failed at t/helper/test-example-tap.c:111\n> +not ok 26 - for_test TEST_TODO() after failing check\n> +# check \"0\" failed at t/helper/test-example-tap.c:117\n> +not ok 27 - for_test failing check after TEST_TODO()\n> +# check \"!strcmp(\"\\thello\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:120\n> +#    left: \"\\011hello\\\\\"\n> +#   right: \"there\\\"\\012\"\n> +# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:121\n> +#    left: \"NULL\"\n> +#   right: NULL\n> +# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:122\n> +#    left: 'a'\n> +#   right: '\\012'\n> +# check \"'\\\\' == '\\''\" failed at t/helper/test-example-tap.c:123\n> +#    left: '\\\\'\n> +#   right: '\\''\n> +not ok 28 - for_test messages from failing string and char comparison\n> +# BUG: test has no checks at t/helper/test-example-tap.c:125\n> +not ok 29 - for_test test with no checks\n> +1..29\n> diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h\n> index 2de6d715d5..12afd47ac9 100644\n> --- a/t/unit-tests/test-lib.h\n> +++ b/t/unit-tests/test-lib.h\n> @@ -14,6 +14,25 @@\n>         test__run_end(test__run_begin() ? 0 : (t, 1),   \\\n>                       TEST_LOCATION(),  __VA_ARGS__)\n>\n> +/*\n> + * Run a test unless test_skip_all() has been called.  Acts like a for\n> + * loop that runs at most once, with the test description between the\n> + * parentheses and the test body as a statement or block after them.\n> + * The description for each test should be unique.  E.g.:\n> + *\n> + *  for_test (\"something else %d %d\", arg1, arg2) {\n> + *          prepare();\n> + *          test_something_else(arg1, arg2);\n> + *          cleanup();\n> + *  }\n> + */\n> +#define for_test(...)                                                  \\\n> +       for (int for_test_running_ = test__run_begin() ?                \\\n> +               (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n> +            for_test_running_;                                         \\\n> +            test__run_end(1, TEST_LOCATION(), __VA_ARGS__),            \\\n> +               for_test_running_ = 0)\n\nIMHO: this is borderline \"too much magic\" for my tastes. I think\nhaving multiple test functions is generally easier to understand, and\nthe overhead isn't really relevant. It's not _as_ compact in the\nsource file, and requires that we have both the TEST statement and the\nfunction (and forgetting the TEST statement means that we won't invoke\nthe function). If that is the main issue we're facing here, I wonder\nif there's some other way of resolving that (such as unused function\ndetection via some compiler flags; even if it's not detected on all\nplatforms, getting at least one of the CI platforms should be\nsufficient to prevent the issue [but we should target as many as\npossible, so it's caught earlier than \"I tried to send it to the\nlist\"])\n\nIf others agree that this is a good simplification for the people\nreading the test code (and hopefully for the test author), I'm fine\nwith this going in (with a different name). I'm not trying to veto the\nconcept.\n\n\n> +\n>  /*\n>   * Print a test plan, should be called before any tests. If the number\n>   * of tests is not known in advance test_done() will automatically\n> --\n> 2.45.2\n>\n"},{"id":"499078","messageId":"xmqq1q3lb4me.fsf@gitster.g","threadId":"61700","inReplyTo":"CAO_smVhoobWpsbYHnHJqTj7TJJ1udo_UaGdbOnUqe5jzL+tyaQ@mail.gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-22T19:36:57Z","receivedAt":"2024-07-22T19:37:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kyle Lippincott <spectral@google.com> writes:\n\n> I can see based on this description where the name came from, but\n> without this context, it's not clear when reading a test what it\n> actually does. The name comes from an implementation detail, and is\n> not describing what it _does_, just _how_ it does it.\n>\n> Maybe a name like `small_test` or `quick_test` or something like that\n> would better express the intended usage?\n\nNames that explicitly have C keyword for control structures, e.g.\n\"if_somecondition_test()\", \"while_foo_test()\" or \"for_test()\" is\nvastly preferrable than these, in order to make it clear that we are\nintroducing a macro that defines control structure.\n\n>> +       for_test (\"for_test passing test\")\n>> +               check_int(1, ==, 1);\n>\n> I'm concerned that users will write this like:\n> +       for_test (\"for_test passing test\");\n> +               check_int(1, ==, 1);\n\nAnd that is exactly why we want the macro name to include C keyword \nfor control structures.\n\n> And the issue won't be caught.\n\nYou are right.  Making an empty body somehow catchable by the\ncompiler would be a vast improvement.\n\n>> +#define for_test(...)                                                  \\\n>> +       for (int for_test_running_ = test__run_begin() ?                \\\n>> +               (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n>> +            for_test_running_;                                         \\\n>> +            test__run_end(1, TEST_LOCATION(), __VA_ARGS__),            \\\n>> +               for_test_running_ = 0)\n>\n> IMHO: this is borderline \"too much magic\" for my tastes. I think\n> having multiple test functions is generally easier to understand, and\n> the overhead isn't really relevant. It's not _as_ compact in the\n> source file, and requires that we have both the TEST statement and the\n> function (and forgetting the TEST statement means that we won't invoke\n> the function). If that is the main issue we're facing here, I wonder\n> if there's some other way of resolving that (such as unused function\n> detection via some compiler flags; even if it's not detected on all\n> platforms, getting at least one of the CI platforms should be\n> sufficient to prevent the issue [but we should target as many as\n> possible, so it's caught earlier than \"I tried to send it to the\n> list\"])\n\nInteresting.\n\n> If others agree that this is a good simplification for the people\n> reading the test code (and hopefully for the test author), I'm fine\n> with this going in (with a different name). I'm not trying to veto the\n> concept.\n\nOK.  But what you suggested in the previous paragraph has merit.\nAre there other things that could be improved in the existing unit\ntest helpers, that would help those who do not use this new for_test()\nthing?  Let's see how the patches to improve them would look like.\n\nThanks.\n\n"},{"id":"499080","messageId":"be282096-49af-4d83-b5f7-8b4f1b447edb@web.de","threadId":"61700","inReplyTo":"xmqq1q3lb4me.fsf@gitster.g","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-22T20:31:48Z","receivedAt":"2024-07-22T20:32:05Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 22.07.24 um 21:36 schrieb Junio C Hamano:\n> Kyle Lippincott <spectral@google.com> writes:\n>\n>>> +       for_test (\"for_test passing test\")\n>>> +               check_int(1, ==, 1);\n>>\n>> I'm concerned that users will write this like:\n>> +       for_test (\"for_test passing test\");\n>> +               check_int(1, ==, 1);\n>\n> And that is exactly why we want the macro name to include C keyword\n> for control structures.\n>\n>> And the issue won't be caught.\n>\n> You are right.  Making an empty body somehow catchable by the\n> compiler would be a vast improvement.\n\nThat would be nice, but I have no idea how to do that without compiler\nchanges.  In the meantime the existing runtime checks will catch both\nthe empty test in the first line and the out-of-test check in the second\none and report them like this:\n\n # BUG: test has no checks at t/helper/test-example-tap.c:75\n not ok 1 - for_test passing test\n Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n\nFile name and line in the second one are not as helpful as they could\nbe.  Here's a patch to improve that output.\n\n--- >8 ---\nSubject: [PATCH] unit-tests: show location of checks outside of tests\n\nChecks outside of tests are caught at runtime and reported like this:\n\n Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n\nThe assert() call aborts the unit test and doesn't reveal the location\nor even the type of the offending check, as test_assert() is called by\nall of them.\n\nHandle it like the opposite case, a test without any checks: Don't\nabort, but report the location of the actual check, along with a message\nexplaining the situation.  The output for example above becomes:\n\n # BUG: check outside of test at t/helper/test-example-tap.c:75\n\n... and the unit test program continues.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/test-lib.c | 6 +++++-\n 1 file changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\nindex 3c513ce59a..9977c81739 100644\n--- a/t/unit-tests/test-lib.c\n+++ b/t/unit-tests/test-lib.c\n@@ -264,7 +264,11 @@ static void test_todo(void)\n\n int test_assert(const char *location, const char *check, int ok)\n {\n-\tassert(ctx.running);\n+\tif (!ctx.running) {\n+\t\ttest_msg(\"BUG: check outside of test at %s\",\n+\t\t\t make_relative(location));\n+\t\treturn 0;\n+\t}\n\n \tif (ctx.result == RESULT_SKIP) {\n \t\ttest_msg(\"skipping check '%s' at %s\", check,\n--\n2.45.2\n"},{"id":"499081","messageId":"xmqqwmld9n2b.fsf@gitster.g","threadId":"61700","inReplyTo":"be282096-49af-4d83-b5f7-8b4f1b447edb@web.de","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-22T20:41:32Z","receivedAt":"2024-07-22T20:41:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n>>> And the issue won't be caught.\n>>\n>> You are right.  Making an empty body somehow catchable by the\n>> compiler would be a vast improvement.\n>\n> That would be nice, but I have no idea how to do that without compiler\n> changes.\n\nMe neither.  I was trying to nerd-snipe Kyle into coming up with a\nsolution ;-)\n\n> In the meantime the existing runtime checks will catch both\n> the empty test in the first line and the out-of-test check in the second\n> one and report them like this:\n>\n>  # BUG: test has no checks at t/helper/test-example-tap.c:75\n>  not ok 1 - for_test passing test\n>  Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n\nNice improvement, I would say.\n\n> diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\n> index 3c513ce59a..9977c81739 100644\n> --- a/t/unit-tests/test-lib.c\n> +++ b/t/unit-tests/test-lib.c\n> @@ -264,7 +264,11 @@ static void test_todo(void)\n>\n>  int test_assert(const char *location, const char *check, int ok)\n>  {\n> -\tassert(ctx.running);\n> +\tif (!ctx.running) {\n> +\t\ttest_msg(\"BUG: check outside of test at %s\",\n> +\t\t\t make_relative(location));\n> +\t\treturn 0;\n> +\t}\n>\n>  \tif (ctx.result == RESULT_SKIP) {\n>  \t\ttest_msg(\"skipping check '%s' at %s\", check,\n> --\n> 2.45.2\n"},{"id":"499090","messageId":"CAO_smViu2AtdU3GceEJkGXK-pXZr+QyvrWk5sYRVLypyFp1PeA@mail.gmail.com","threadId":"61700","inReplyTo":"xmqq1q3lb4me.fsf@gitster.g","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-22T22:41:31Z","receivedAt":"2024-07-22T22:41:48Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Mon, Jul 22, 2024 at 12:37 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Kyle Lippincott <spectral@google.com> writes:\n>\n> > I can see based on this description where the name came from, but\n> > without this context, it's not clear when reading a test what it\n> > actually does. The name comes from an implementation detail, and is\n> > not describing what it _does_, just _how_ it does it.\n> >\n> > Maybe a name like `small_test` or `quick_test` or something like that\n> > would better express the intended usage?\n>\n> Names that explicitly have C keyword for control structures, e.g.\n> \"if_somecondition_test()\", \"while_foo_test()\" or \"for_test()\" is\n> vastly preferrable than these, in order to make it clear that we are\n> introducing a macro that defines control structure.\n\nPerhaps expression_test or something, then? It's testing an expression\n(I think blocks are a type of expression? I never remember which is\n'larger': expressions or statements, and that might only be a C++\nthing, C might not have the same terminology).\n\nI was going to suggest a lint check that checks to ensure that we\ndon't have a semicolon immediately after the description, or a lint\ncheck that enforced that even single-statement tests are wrapped in {}\n(inverting the style guide requirements), but realistically neither\nare actually needed: `test__run_begin` already asserts that a test\nisn't currently running. `check_int` and friends already assert that a\ntest _is_ running. So this is already defended against:\n\nfor_test (\"test description\");\n    check_int(1, ==, 1);   /* `assert`s: not in the middle of a test */\n\nWhat we don't have is defense against a completely empty test, or a\ntest without any actual conditions:\n\nfor_test (\"test one\");\nfor_test (\"test two\") {\n    printf(\"this test doesn't actually _test_ anything\\n\");\n}\n\nAdding that is doable, and improves the first case: the `for_test\n(\"test description\");` line fails because the test didn't do anything,\nnot the `check_int` line.\n\n>\n> >> +       for_test (\"for_test passing test\")\n> >> +               check_int(1, ==, 1);\n> >\n> > I'm concerned that users will write this like:\n> > +       for_test (\"for_test passing test\");\n> > +               check_int(1, ==, 1);\n>\n> And that is exactly why we want the macro name to include C keyword\n> for control structures.\n>\n> > And the issue won't be caught.\n>\n> You are right.  Making an empty body somehow catchable by the\n> compiler would be a vast improvement.\n>\n> >> +#define for_test(...)                                                  \\\n> >> +       for (int for_test_running_ = test__run_begin() ?                \\\n> >> +               (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n> >> +            for_test_running_;                                         \\\n> >> +            test__run_end(1, TEST_LOCATION(), __VA_ARGS__),            \\\n> >> +               for_test_running_ = 0)\n> >\n> > IMHO: this is borderline \"too much magic\" for my tastes. I think\n> > having multiple test functions is generally easier to understand, and\n> > the overhead isn't really relevant. It's not _as_ compact in the\n> > source file, and requires that we have both the TEST statement and the\n> > function (and forgetting the TEST statement means that we won't invoke\n> > the function). If that is the main issue we're facing here, I wonder\n> > if there's some other way of resolving that (such as unused function\n> > detection via some compiler flags; even if it's not detected on all\n> > platforms, getting at least one of the CI platforms should be\n> > sufficient to prevent the issue [but we should target as many as\n> > possible, so it's caught earlier than \"I tried to send it to the\n> > list\"])\n>\n> Interesting.\n>\n> > If others agree that this is a good simplification for the people\n> > reading the test code (and hopefully for the test author), I'm fine\n> > with this going in (with a different name). I'm not trying to veto the\n> > concept.\n>\n> OK.  But what you suggested in the previous paragraph has merit.\n> Are there other things that could be improved in the existing unit\n> test helpers, that would help those who do not use this new for_test()\n> thing?  Let's see how the patches to improve them would look like.\n>\n> Thanks.\n>\n"},{"id":"499091","messageId":"CAO_smViM7_Er4hY_WvxJf_xWGtKVN29gJA9APzW3t3GdAaxsgQ@mail.gmail.com","threadId":"61700","inReplyTo":"be282096-49af-4d83-b5f7-8b4f1b447edb@web.de","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-22T22:47:57Z","receivedAt":"2024-07-22T22:48:11Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Mon, Jul 22, 2024 at 1:31 PM René Scharfe <l.s.r@web.de> wrote:\n>\n> Am 22.07.24 um 21:36 schrieb Junio C Hamano:\n> > Kyle Lippincott <spectral@google.com> writes:\n> >\n> >>> +       for_test (\"for_test passing test\")\n> >>> +               check_int(1, ==, 1);\n> >>\n> >> I'm concerned that users will write this like:\n> >> +       for_test (\"for_test passing test\");\n> >> +               check_int(1, ==, 1);\n> >\n> > And that is exactly why we want the macro name to include C keyword\n> > for control structures.\n> >\n> >> And the issue won't be caught.\n> >\n> > You are right.  Making an empty body somehow catchable by the\n> > compiler would be a vast improvement.\n>\n> That would be nice, but I have no idea how to do that without compiler\n> changes.  In the meantime the existing runtime checks will catch both\n> the empty test in the first line and the out-of-test check in the second\n> one and report them like this:\n>\n>  # BUG: test has no checks at t/helper/test-example-tap.c:75\n>  not ok 1 - for_test passing test\n>  Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n>\n> File name and line in the second one are not as helpful as they could\n> be.  Here's a patch to improve that output.\n>\n> --- >8 ---\n> Subject: [PATCH] unit-tests: show location of checks outside of tests\n>\n> Checks outside of tests are caught at runtime and reported like this:\n>\n>  Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n>\n> The assert() call aborts the unit test and doesn't reveal the location\n> or even the type of the offending check, as test_assert() is called by\n> all of them.\n>\n> Handle it like the opposite case, a test without any checks: Don't\n> abort, but report the location of the actual check, along with a message\n> explaining the situation.  The output for example above becomes:\n\nOh, I referenced \"detect tests without checks\" as a possible\nimprovement in my previous message, and didn't actually check whether\nit was there. This statement made me go and check, and you're right,\nit is there already. So I think we're well defended against the empty\ncheck case, especially with the improvement here. Thanks!\n\nIf we have a good set of protections against misuse, does this mean\nwe're free to rename it? :) The concern raised for why `for` had to be\nin the name was because it was using a control statement (a\nat-most-1-execution for loop) to achieve its magic, and the control\nstatement puts certain restrictions and obligations on how to use it\ncorrectly. If the misuse is detected reliably, we can choose a name\nthat's more descriptive about what it's doing.\n\n>\n>  # BUG: check outside of test at t/helper/test-example-tap.c:75\n>\n> ... and the unit test program continues.\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  t/unit-tests/test-lib.c | 6 +++++-\n>  1 file changed, 5 insertions(+), 1 deletion(-)\n>\n> diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\n> index 3c513ce59a..9977c81739 100644\n> --- a/t/unit-tests/test-lib.c\n> +++ b/t/unit-tests/test-lib.c\n> @@ -264,7 +264,11 @@ static void test_todo(void)\n>\n>  int test_assert(const char *location, const char *check, int ok)\n>  {\n> -       assert(ctx.running);\n> +       if (!ctx.running) {\n> +               test_msg(\"BUG: check outside of test at %s\",\n> +                        make_relative(location));\n> +               return 0;\n> +       }\n>\n>         if (ctx.result == RESULT_SKIP) {\n>                 test_msg(\"skipping check '%s' at %s\", check,\n> --\n> 2.45.2\n"},{"id":"499125","messageId":"d92e5854-e916-49eb-a79e-9dc43778a7ea@web.de","threadId":"61700","inReplyTo":"be282096-49af-4d83-b5f7-8b4f1b447edb@web.de","subject":"[PATCH v2] unit-tests: show location of checks outside of tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-23T06:02:42Z","receivedAt":"2024-07-23T06:02:54Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Checks outside of tests are caught at runtime and reported like this:\n\n Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n\nThe assert() call aborts the unit test and doesn't reveal the location\nor even the type of the offending check, as test_assert() is called by\nall of them.\n\nHandle it like the opposite case, a test without any checks: Don't\nabort, but report the location of the actual check, along with a message\nexplaining the situation.  The output for example above becomes:\n\n # BUG: check outside of test at t/helper/test-example-tap.c:75\n\n... and the unit test program continues and indicates the error in its\nexit code at the end.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\nChanges since v1:\n- Set ctx.failed to report the mistake via exit code as well.\n\n t/unit-tests/test-lib.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\nindex 3c513ce59a..989dc758e6 100644\n--- a/t/unit-tests/test-lib.c\n+++ b/t/unit-tests/test-lib.c\n@@ -264,7 +264,12 @@ static void test_todo(void)\n\n int test_assert(const char *location, const char *check, int ok)\n {\n-\tassert(ctx.running);\n+\tif (!ctx.running) {\n+\t\ttest_msg(\"BUG: check outside of test at %s\",\n+\t\t\t make_relative(location));\n+\t\tctx.failed = 1;\n+\t\treturn 0;\n+\t}\n\n \tif (ctx.result == RESULT_SKIP) {\n \t\ttest_msg(\"skipping check '%s' at %s\", check,\n--\n2.45.2\n"},{"id":"499127","messageId":"Zp9PfdZtWJBp2xgl@tanuki","threadId":"61700","inReplyTo":"xmqq1q3lb4me.fsf@gitster.g","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-23T06:36:45Z","receivedAt":"2024-07-23T06:36:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Jul 22, 2024 at 12:36:57PM -0700, Junio C Hamano wrote:\n> Kyle Lippincott <spectral@google.com> writes:\n> >> +#define for_test(...)                                                  \\\n> >> +       for (int for_test_running_ = test__run_begin() ?                \\\n> >> +               (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n> >> +            for_test_running_;                                         \\\n> >> +            test__run_end(1, TEST_LOCATION(), __VA_ARGS__),            \\\n> >> +               for_test_running_ = 0)\n> >\n> > IMHO: this is borderline \"too much magic\" for my tastes. I think\n> > having multiple test functions is generally easier to understand, and\n> > the overhead isn't really relevant. It's not _as_ compact in the\n> > source file, and requires that we have both the TEST statement and the\n> > function (and forgetting the TEST statement means that we won't invoke\n> > the function). If that is the main issue we're facing here, I wonder\n> > if there's some other way of resolving that (such as unused function\n> > detection via some compiler flags; even if it's not detected on all\n> > platforms, getting at least one of the CI platforms should be\n> > sufficient to prevent the issue [but we should target as many as\n> > possible, so it's caught earlier than \"I tried to send it to the\n> > list\"])\n> \n> Interesting.\n> \n> > If others agree that this is a good simplification for the people\n> > reading the test code (and hopefully for the test author), I'm fine\n> > with this going in (with a different name). I'm not trying to veto the\n> > concept.\n> \n> OK.  But what you suggested in the previous paragraph has merit.\n> Are there other things that could be improved in the existing unit\n> test helpers, that would help those who do not use this new for_test()\n> thing?  Let's see how the patches to improve them would look like.\n\nHonestly I'm also not too sure how I feel about these new macros, and\nI'm somewhat in the same boat that it starts to feels \"magicky\".\n\nTaking a step back: what is it that is bugging folks about the current\nway of writing one function per test? The boilerplate cannot really be\nit, as tests should be self-contained anyway and thus we should have the\nsame boilerplate regardless of whether we use separate functions or the\nnew macros. I also doubt that it's the function declaration itself, as\nthat isn't all that involved. So I guess what people are annoyed with is\nthat every function needs to be defined, but then also wired up via\n`TEST_RUN()`, right? And that I totally get, as it is quite annoying.\n\nSo... can we solve this in a different way? I quite liked the system\nthat we had in libgit2: every function must conform to a specific name\n`test_foo_bar`. We then have a Python script that reads all test files,\nextracts all files that have the `test_` prefix, and writes those into\nan array. Optionally, the `test_foo` test suite can also have a setup\nand teardown function that gets called for every test, namely\n`test_foo_setup()` and `test_foo_teardown()`.\n\nAltogether, the output would look somewhat like this:\n\n\n```test.c\nstatic test_foo_setup(void)\n{\n    ... setup global state ...\n}\n\nstatic test_foo_teardown(void)\n{\n    ... tear down global state ...\n}\n\nstatic test_foo_one(void)\n{\n    ... first test ...\n}\n\nstatic test_foo_two(void)\n{\n    ... second test ...\n}\n```\n\n```generated.c\nstatic const struct test_func _test_foo_functions[] = {\n    {\n        name: \"foo::one\",\n        test: test_foo_one,\n        setup: test_foo_setup,\n        teardown: test_foo_teardown,\n    },\n    {\n        name: \"foo::two\",\n        test: test_foo_two,\n        setup: test_foo_setup,\n        teardown: test_foo_teardown,\n    },\n};\n\nint main(int argc, const char *argv[])\n{\n    ... boilerplate to execute all functions ...\n}\n```\n\nThe setup and teardown functions are optional -- if not defined for a\nspecific test unit, then we simply won't invoke them.\n\nThere is of course some magic involved with how we generate the file.\nBut I think that would be quite manageable, and ultimately all that the\ndeveloper would need to care about is writing a `test_foo_something()`\nfunction. Everything else would be handled by our infra.\n\nI'd be happy to implement something along these lines if folks think\nthat this is a good direction to go into.\n\nPatrick\n"},{"id":"499128","messageId":"dc2e7ee2-12db-48ea-ab49-4e09991f72a1@web.de","threadId":"61700","inReplyTo":"CAO_smViu2AtdU3GceEJkGXK-pXZr+QyvrWk5sYRVLypyFp1PeA@mail.gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-23T07:18:18Z","receivedAt":"2024-07-23T07:18:38Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 23.07.24 um 00:41 schrieb Kyle Lippincott:\n> On Mon, Jul 22, 2024 at 12:37 PM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Kyle Lippincott <spectral@google.com> writes:\n>>\n>>> I can see based on this description where the name came from, but\n>>> without this context, it's not clear when reading a test what it\n>>> actually does. The name comes from an implementation detail, and is\n>>> not describing what it _does_, just _how_ it does it.\n>>>\n>>> Maybe a name like `small_test` or `quick_test` or something like that\n>>> would better express the intended usage?\n>>\n>> Names that explicitly have C keyword for control structures, e.g.\n>> \"if_somecondition_test()\", \"while_foo_test()\" or \"for_test()\" is\n>> vastly preferrable than these, in order to make it clear that we are\n>> introducing a macro that defines control structure.\n>\n> Perhaps expression_test or something, then? It's testing an expression\n> (I think blocks are a type of expression? I never remember which is\n> 'larger': expressions or statements, and that might only be a C++\n> thing, C might not have the same terminology).\n\nIf in doubt, feel free to look it up.  We're not taking a test here\n(bad pun intended).\n\nhttps://en.cppreference.com/w/c/language/expressions\nhttps://en.cppreference.com/w/c/language/statements\n\nTEST requires test bodies to be expressions, for_test allows them to\nbe statements.  Statements can include arbitrary expressions, but not\nthe other way around.\n\nThe \"for\" in for_test indicates that it's a for-like macro.  This\nalready implies that it is followed by a statement.  It works for the\nexisting for_each macros.\n\nRené\n"},{"id":"499141","messageId":"71aa553e-9b10-4bc7-9c7d-5414691db79a@web.de","threadId":"61700","inReplyTo":"Zp9PfdZtWJBp2xgl@tanuki","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-23T09:25:29Z","receivedAt":"2024-07-23T09:25:49Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 23.07.24 um 08:36 schrieb Patrick Steinhardt:\n> On Mon, Jul 22, 2024 at 12:36:57PM -0700, Junio C Hamano wrote:\n>> Kyle Lippincott <spectral@google.com> writes:\n>>>> +#define for_test(...)                                                  \\\n>>>> +       for (int for_test_running_ = test__run_begin() ?                \\\n>>>> +               (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n>>>> +            for_test_running_;                                         \\\n>>>> +            test__run_end(1, TEST_LOCATION(), __VA_ARGS__),            \\\n>>>> +               for_test_running_ = 0)\n>>>\n>>> IMHO: this is borderline \"too much magic\" for my tastes. I think\n>>> having multiple test functions is generally easier to understand, and\n>>> the overhead isn't really relevant. It's not _as_ compact in the\n>>> source file, and requires that we have both the TEST statement and the\n>>> function (and forgetting the TEST statement means that we won't invoke\n>>> the function). If that is the main issue we're facing here, I wonder\n>>> if there's some other way of resolving that (such as unused function\n>>> detection via some compiler flags; even if it's not detected on all\n>>> platforms, getting at least one of the CI platforms should be\n>>> sufficient to prevent the issue [but we should target as many as\n>>> possible, so it's caught earlier than \"I tried to send it to the\n>>> list\"])\n>>\n>> Interesting.\n>>\n>>> If others agree that this is a good simplification for the people\n>>> reading the test code (and hopefully for the test author), I'm fine\n>>> with this going in (with a different name). I'm not trying to veto the\n>>> concept.\n>>\n>> OK.  But what you suggested in the previous paragraph has merit.\n>> Are there other things that could be improved in the existing unit\n>> test helpers, that would help those who do not use this new for_test()\n>> thing?  Let's see how the patches to improve them would look like.\n>\n> Honestly I'm also not too sure how I feel about these new macros, and\n> I'm somewhat in the same boat that it starts to feels \"magicky\".\n>\n> Taking a step back: what is it that is bugging folks about the current\n> way of writing one function per test?\n\nMy goal is to be able to define a test without repeating its\ndescription even partly, like test_expect_success allows for shell-based\ntests.\n\n> I quite liked the system\n> that we had in libgit2: every function must conform to a specific name\n> `test_foo_bar`. We then have a Python script that reads all test files,\n> extracts all files that have the `test_` prefix, and writes those into\n> an array. Optionally, the `test_foo` test suite can also have a setup\n> and teardown function that gets called for every test, namely\n> `test_foo_setup()` and `test_foo_teardown()`.\n>\n> Altogether, the output would look somewhat like this:\n>\n>\n> ```test.c\n> static test_foo_setup(void)\n> {\n>     ... setup global state ...\n> }\n>\n> static test_foo_teardown(void)\n> {\n>     ... tear down global state ...\n> }\n>\n> static test_foo_one(void)\n> {\n>     ... first test ...\n> }\n>\n> static test_foo_two(void)\n> {\n>     ... second test ...\n> }\n> ```\n>\n> ```generated.c\n> static const struct test_func _test_foo_functions[] = {\n>     {\n>         name: \"foo::one\",\n>         test: test_foo_one,\n>         setup: test_foo_setup,\n>         teardown: test_foo_teardown,\n>     },\n>     {\n>         name: \"foo::two\",\n>         test: test_foo_two,\n>         setup: test_foo_setup,\n>         teardown: test_foo_teardown,\n>     },\n> };\n>\n> int main(int argc, const char *argv[])\n> {\n>     ... boilerplate to execute all functions ...\n> }\n> ```\n>\n> The setup and teardown functions are optional -- if not defined for a\n> specific test unit, then we simply won't invoke them.\n\nSo the name of a test is generated automatically based on the function\nname and there is no way to add a description beyond that.  This would\nachieve my goal above, but prevent developers from using space and\npunctuation in test descriptions.  Fair enough.\n\n> There is of course some magic involved with how we generate the file.\n\nIt requires magic function names and generates code using a different\nlanguage, while for_test is a just single new control flow keyword,\nlike the dozen or so we already have.  So the magic employed by the\nlibgit2 system is both broader and deeper.\n\n> But I think that would be quite manageable, and ultimately all that the\n> developer would need to care about is writing a `test_foo_something()`\n> function. Everything else would be handled by our infra.\n\nWith for_test all the developer has to do is write a test with a\ndescription, no extra infrastructure beyond the existing unit test\nframework needed.\n\n> I'd be happy to implement something along these lines if folks think\n> that this is a good direction to go into.\n\nFWIW I don't see the appeal.  It uses more code and more dependencies\nto almost allow what for_test permits.\n\nRené\n"},{"id":"499143","messageId":"Zp99icyrc0rdxg9a@tanuki","threadId":"61700","inReplyTo":"71aa553e-9b10-4bc7-9c7d-5414691db79a@web.de","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-23T09:53:13Z","receivedAt":"2024-07-23T09:53:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jul 23, 2024 at 11:25:29AM +0200, René Scharfe wrote:\n> Am 23.07.24 um 08:36 schrieb Patrick Steinhardt:\n> > There is of course some magic involved with how we generate the file.\n> \n> It requires magic function names and generates code using a different\n> language, while for_test is a just single new control flow keyword,\n> like the dozen or so we already have.  So the magic employed by the\n> libgit2 system is both broader and deeper.\n\nIt is broader, that's certainly true. But it feels more self-contained,\nless fragile and easier to read to me compared to macros.\n\n> > But I think that would be quite manageable, and ultimately all that the\n> > developer would need to care about is writing a `test_foo_something()`\n> > function. Everything else would be handled by our infra.\n> \n> With for_test all the developer has to do is write a test with a\n> description, no extra infrastructure beyond the existing unit test\n> framework needed.\n\nTrue, but it feels like it is an invitation for writing unidiomatic\ncode to me. Unidiomatic in this context to me mostly means code that is\nnot self-contained and thus cannot be run standalone.\n\nThese are quite obviously subjective opinions though.\n\nPatrick\n"},{"id":"499148","messageId":"789d99ab-8d39-4f5f-a800-6ed5dd19d23e@web.de","threadId":"61700","inReplyTo":"CAO_smViM7_Er4hY_WvxJf_xWGtKVN29gJA9APzW3t3GdAaxsgQ@mail.gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-23T12:37:38Z","receivedAt":"2024-07-23T12:37:47Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 23.07.24 um 00:47 schrieb Kyle Lippincott:\n>\n> If we have a good set of protections against misuse, does this mean\n> we're free to rename it? :) The concern raised for why `for` had to be\n> in the name was because it was using a control statement (a\n> at-most-1-execution for loop) to achieve its magic, and the control\n> statement puts certain restrictions and obligations on how to use it\n> correctly. If the misuse is detected reliably, we can choose a name\n> that's more descriptive about what it's doing.\n\nWell, I called it \"test\" initially because I'm lazy and it describes\nits purpose.\n\nBut the fact remains that this is an iteration statement, even though\nit kinda looks like a function, and it should be used as such.  We have\nto explicitly tell clang-format, and having \"for\" in the name reminds\ndevelopers as well.\n\nThere is a slight dissonance between it executing at most once and it\nbeing an iteration.  In C we commonly use conditionals (\"if\") for that.\nCalling it \"if_test\" would be misleading with \"for\" under the hood,\nthough, as you cannot have an \"else\" branch -- which we don't need\nanyway.\n\nThat said, iterating zero or one times is possible and understandable\nnot too much of a stretch IMHO.  I can understand that one needs a\nminute to get used to that new keyword, though.\n\nRené\n"},{"id":"499149","messageId":"0af06e8e-e1d7-4cf6-b968-5dad5f4fef2e@web.de","threadId":"61700","inReplyTo":"Zp99icyrc0rdxg9a@tanuki","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-23T12:37:35Z","receivedAt":"2024-07-23T12:37:50Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 23.07.24 um 11:53 schrieb Patrick Steinhardt:\n> On Tue, Jul 23, 2024 at 11:25:29AM +0200, René Scharfe wrote:\n>> Am 23.07.24 um 08:36 schrieb Patrick Steinhardt:\n>>> There is of course some magic involved with how we generate the file.\n>>\n>> It requires magic function names and generates code using a different\n>> language, while for_test is a just single new control flow keyword,\n>> like the dozen or so we already have.  So the magic employed by the\n>> libgit2 system is both broader and deeper.\n>\n> It is broader, that's certainly true. But it feels more self-contained,\n> less fragile and easier to read to me compared to macros.\n\nIn which ways can for_test break?\n\n>>> But I think that would be quite manageable, and ultimately all that the\n>>> developer would need to care about is writing a `test_foo_something()`\n>>> function. Everything else would be handled by our infra.\n>>\n>> With for_test all the developer has to do is write a test with a\n>> description, no extra infrastructure beyond the existing unit test\n>> framework needed.\n>\n> True, but it feels like it is an invitation for writing unidiomatic\n> code to me. Unidiomatic in this context to me mostly means code that is\n> not self-contained and thus cannot be run standalone.\n\nPartly this is intentional -- I want to do something that the current\nidiom (the TEST macro) doesn't allow.\n\nIt's possible that reducing friction will cause more sloppy tests to\nbe created.  I hope that the bad parts will stand out more when\nthere's less boilerplate code.\n\nRené\n"},{"id":"499150","messageId":"Zp-pVsWeTCbfcquh@tanuki","threadId":"61700","inReplyTo":"0af06e8e-e1d7-4cf6-b968-5dad5f4fef2e@web.de","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-23T13:00:06Z","receivedAt":"2024-07-23T13:00:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jul 23, 2024 at 02:37:35PM +0200, René Scharfe wrote:\n> Am 23.07.24 um 11:53 schrieb Patrick Steinhardt:\n> > On Tue, Jul 23, 2024 at 11:25:29AM +0200, René Scharfe wrote:\n> >> Am 23.07.24 um 08:36 schrieb Patrick Steinhardt:\n> >>> There is of course some magic involved with how we generate the file.\n> >>\n> >> It requires magic function names and generates code using a different\n> >> language, while for_test is a just single new control flow keyword,\n> >> like the dozen or so we already have.  So the magic employed by the\n> >> libgit2 system is both broader and deeper.\n> >\n> > It is broader, that's certainly true. But it feels more self-contained,\n> > less fragile and easier to read to me compared to macros.\n> \n> In which ways can for_test break?\n\nI was mostly referring to the potential for empty-by-accident test\nbodies that were mentioned in other parts of this thread.\n\nPatrick\n"},{"id":"499151","messageId":"55857c83-2181-4dcb-b354-f9f7e77cd6b9@gmail.com","threadId":"61700","inReplyTo":"0af06e8e-e1d7-4cf6-b968-5dad5f4fef2e@web.de","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-23T13:23:44Z","receivedAt":"2024-07-23T13:23:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 23/07/2024 13:37, René Scharfe wrote:\n> Am 23.07.24 um 11:53 schrieb Patrick Steinhardt:\n>> On Tue, Jul 23, 2024 at 11:25:29AM +0200, René Scharfe wrote:\n>>> Am 23.07.24 um 08:36 schrieb Patrick Steinhardt:\n>>>> There is of course some magic involved with how we generate the file.\n>>>\n>>> It requires magic function names and generates code using a different\n>>> language, while for_test is a just single new control flow keyword,\n>>> like the dozen or so we already have.  So the magic employed by the\n>>> libgit2 system is both broader and deeper.\n>>\n>> It is broader, that's certainly true. But it feels more self-contained,\n>> less fragile and easier to read to me compared to macros.\n> \n> In which ways can for_test break?\n\nUsing a \"break\" statement to exit the test early will exit the loop \nwithout calling test__run_end()\n\nBest Wishes\n\nPhillip\n\n> \n>>>> But I think that would be quite manageable, and ultimately all that the\n>>>> developer would need to care about is writing a `test_foo_something()`\n>>>> function. Everything else would be handled by our infra.\n>>>\n>>> With for_test all the developer has to do is write a test with a\n>>> description, no extra infrastructure beyond the existing unit test\n>>> framework needed.\n>>\n>> True, but it feels like it is an invitation for writing unidiomatic\n>> code to me. Unidiomatic in this context to me mostly means code that is\n>> not self-contained and thus cannot be run standalone.\n> \n> Partly this is intentional -- I want to do something that the current\n> idiom (the TEST macro) doesn't allow.\n> \n> It's possible that reducing friction will cause more sloppy tests to\n> be created.  I hope that the bad parts will stand out more when\n> there's less boilerplate code.\n> \n> René\n> \n\n"},{"id":"499152","messageId":"d51c9764-e11e-452d-8974-7de80ac34195@gmail.com","threadId":"61700","inReplyTo":"b179d004-d00b-4ed7-8cc9-f3b388f101f4@web.de","subject":"Re: [PATCH v2 6/6] t-strbuf: use for_test","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-23T13:23:57Z","receivedAt":"2024-07-23T13:23:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi René\n\nOn 21/07/2024 07:26, René Scharfe wrote:\n\nI'm still ambivalent about patches 3-6 but if we do this can we visually \nseparate the initialization and cleanup from the main test body please. \nFor example\n\n> +\tfor_test (\"strbuf_addch adds char\") {\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n+\n> +\t\tt_addch(&sb, 'a');\n+\n> +\t\tt_release(&sb);\n> +\t}\n\nBest Wishes\n\nPhillip\n\n"},{"id":"499153","messageId":"b4cbc8ce-5e39-491d-a6c9-627a7fd97c8f@gmail.com","threadId":"61700","inReplyTo":"CAO_smVhoobWpsbYHnHJqTj7TJJ1udo_UaGdbOnUqe5jzL+tyaQ@mail.gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-23T13:24:31Z","receivedAt":"2024-07-23T13:24:33Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 22/07/2024 20:13, Kyle Lippincott wrote:\n> On Sat, Jul 20, 2024 at 11:22 PM René Scharfe <l.s.r@web.de> wrote:\n>>\n>> The macro TEST only allows defining a test that consists of a single\n>> expression.  Add a new macro, for_test, which provides a way to define\n>> unit tests that are made up of one or more statements.\n>>\n>> for_test allows defining self-contained tests en bloc, a bit like\n>> test_expect_success does for regular tests.  It acts like a for loop\n>> that runs at most once; the test body is executed if test_skip_all()\n>> had not been called before.\n> \n> I can see based on this description where the name came from, but\n> without this context, it's not clear when reading a test what it\n> actually does. The name comes from an implementation detail, and is\n> not describing what it _does_, just _how_ it does it.\n\nThat's my feeling too. In fact I think the name \"for_test\" actually \nworks against us by implicitly suggesting that \"break\" can be used to \nexit the test early when in fact that will not work because we'll skip \nthe call to test__run_end()\n\n>> + * Run a test unless test_skip_all() has been called.  Acts like a for\n>> + * loop that runs at most once, with the test description between the\n>> + * parentheses and the test body as a statement or block after them.\n>> + * The description for each test should be unique.  E.g.:\n>> + *\n>> + *  for_test (\"something else %d %d\", arg1, arg2) {\n>> + *          prepare();\n>> + *          test_something_else(arg1, arg2);\n>> + *          cleanup();\n>> + *  }\n>> + */\n>> +#define for_test(...)                                                  \\\n>> +       for (int for_test_running_ = test__run_begin() ?                \\\n>> +               (test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n>> +            for_test_running_;                                         \\\n>> +            test__run_end(1, TEST_LOCATION(), __VA_ARGS__),            \\\n>> +               for_test_running_ = 0)\n> \n> IMHO: this is borderline \"too much magic\" for my tastes.\n\nYes, compared to TEST_RUN() in v1 the magic level has jumped from Muggle \nto Dumbledore. Using a for loop is clever as it ensures test__run_end() \nis called at the end of the test without the need for separate \nTEST_END()[1] macro. The alternative is something like \ntest_if_enabled()[2] which was discussed in the last round.\n\n[1] \nhttps://lore.kernel.org/git/4f41f509-1d44-4476-92b0-9bb643f64576@gmail.com\n[2] https://lore.kernel.org/git/xmqqa5iot01s.fsf@gitster.g\n\n> I think\n> having multiple test functions is generally easier to understand, and\n> the overhead isn't really relevant. It's not _as_ compact in the\n> source file, and requires that we have both the TEST statement and the\n> function (and forgetting the TEST statement means that we won't invoke\n> the function). If that is the main issue we're facing here, I wonder\n> if there's some other way of resolving that (such as unused function\n> detection via some compiler flags; even if it's not detected on all\n> platforms, getting at least one of the CI platforms should be\n> sufficient to prevent the issue [but we should target as many as\n> possible, so it's caught earlier than \"I tried to send it to the\n> list\"])\n\nI also have a preference for function based tests but I do think \nsomething like for_test() can be useful in certain situations. For \nexample in test-ctype.c where testing the ctype macros leads to a lot of \nrepetition or a giant macro with a function based approach. If isalpha() \nand friends were functions instead we could write a single helper \nfunction which is passed the function under test together with the input \ndata and expect result. Because they are macros that approach does not work.\n\nAnother example is where we are using a helper function with several \ninputs and we would prefer to write\n\nfor_test(\"test 1\") {\n\tint input[] = ...\n\tint expect[] = ...\n\n\ttest_helper(input, expect);\n\n...\n\nfor_test(\"test 10\") {\n\tint input[] = ...\n\tint expect[] = ...\n\n\ttest_helper(input, expect);\n}\n\nrather then declaring all the input and expect variables up front with\n\n\tint input_1 = ...\n\tint input_2 = ...\n\t...\n\tint expect_1 = ...\n\tint expect_2 = ...\n\n\tTEST(test_helper(input_1, expect_1), \"test 1\");\n\t...\n\tTEST(test_helper(input_10, expect_10), \"test 10\");\n\n> If others agree that this is a good simplification for the people\n> reading the test code (and hopefully for the test author), I'm fine\n> with this going in (with a different name). I'm not trying to veto the\n> concept.\n\nThat's my feeling too.\n\nBest Wishes\n\nPhillip\n\n> \n>> +\n>>   /*\n>>    * Print a test plan, should be called before any tests. If the number\n>>    * of tests is not known in advance test_done() will automatically\n>> --\n>> 2.45.2\n>>\n> \n\n"},{"id":"499154","messageId":"ef7c75d3-ed09-485e-a094-17a74de487d7@gmail.com","threadId":"61700","inReplyTo":"d92e5854-e916-49eb-a79e-9dc43778a7ea@web.de","subject":"Re: [PATCH v2] unit-tests: show location of checks outside of tests","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-23T13:25:15Z","receivedAt":"2024-07-23T13:25:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 23/07/2024 07:02, René Scharfe wrote:\n> Checks outside of tests are caught at runtime and reported like this:\n> \n>   Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n> \n> The assert() call aborts the unit test and doesn't reveal the location\n> or even the type of the offending check, as test_assert() is called by\n> all of them.\n> \n> Handle it like the opposite case, a test without any checks: Don't\n> abort, but report the location of the actual check, along with a message\n> explaining the situation.  The output for example above becomes:\n> \n>   # BUG: check outside of test at t/helper/test-example-tap.c:75\n> \n> ... and the unit test program continues and indicates the error in its\n> exit code at the end.\n\nThis is a nice idea and looks good\n\nThanks\n\nPhillip\n\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n> Changes since v1:\n> - Set ctx.failed to report the mistake via exit code as well.\n> \n>   t/unit-tests/test-lib.c | 7 ++++++-\n>   1 file changed, 6 insertions(+), 1 deletion(-)\n> \n> diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\n> index 3c513ce59a..989dc758e6 100644\n> --- a/t/unit-tests/test-lib.c\n> +++ b/t/unit-tests/test-lib.c\n> @@ -264,7 +264,12 @@ static void test_todo(void)\n> \n>   int test_assert(const char *location, const char *check, int ok)\n>   {\n> -\tassert(ctx.running);\n> +\tif (!ctx.running) {\n> +\t\ttest_msg(\"BUG: check outside of test at %s\",\n> +\t\t\t make_relative(location));\n> +\t\tctx.failed = 1;\n> +\t\treturn 0;\n> +\t}\n> \n>   \tif (ctx.result == RESULT_SKIP) {\n>   \t\ttest_msg(\"skipping check '%s' at %s\", check,\n> --\n> 2.45.2\n> \n\n"},{"id":"499156","messageId":"4ccfab92-e66c-40d4-be56-e3eb04374670@web.de","threadId":"61700","inReplyTo":"55857c83-2181-4dcb-b354-f9f7e77cd6b9@gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-23T13:58:18Z","receivedAt":"2024-07-23T13:58:34Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 23.07.24 um 15:23 schrieb Phillip Wood:\n> On 23/07/2024 13:37, René Scharfe wrote:\n>>\n>> In which ways can for_test break?\n>\n> Using a \"break\" statement to exit the test early will exit the loop\n> without calling test__run_end()\nGood point!  That will only be caught at runtime by the asserts at the\nstart of test__run_begin() or test_done() depending on whether it was\nthe last test:\n\n Assertion failed: (!ctx.running), function test_done, file test-lib.c, line 128.\n\n Assertion failed: (!ctx.running), function test__run_begin, file test-lib.c, line 172.\n\nThese messages could be more helpful.  And the use of the unsupported\nbreak with a first-class for-like keyword would yield a compiler error.\nBut at least it won't go unnoticed even with this limited look-alike.\nDeserves a comment in test-lib.h.\n\nRené\n"},{"id":"499199","messageId":"20240723205414.GC6779@coredump.intra.peff.net","threadId":"61700","inReplyTo":"af5b8fef-073a-4e85-b660-9672b7d5f629@web.de","subject":"Re: [PATCH v2 1/6] t0080: move expected output to a file","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-07-23T20:54:14Z","receivedAt":"2024-07-23T20:54:16Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Jul 21, 2024 at 08:15:48AM +0200, René Scharfe wrote:\n\n> Provide the expected output of \"test-tool example-tap\" verbatim instead\n> of as a here-doc, to avoid distractions due to quoting, variables\n> containing quotes and indentation.\n\nJust to bring our earlier conversation full circle, you can do now do\nthis with:\n\n  test_expect_success 'TAP output from unit tests' - <<\\EOT\n\tcat >expect <<-\\EOF\n\t...\n\tEOF\n  EOT\n\nsince those patches are in master. But I'm OK if you don't want to\nbother with it.\n\n-Peff\n"},{"id":"499260","messageId":"73465c3d-1be0-456b-9471-f875e819c566@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH v3 0/7] add and use for_test to simplify tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:42:42Z","receivedAt":"2024-07-24T14:42:54Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Changes since v2:\n- use nested here-docs in t0080 to reduce quoting and while keeping it\n  self-contained,\n- include spin-off patch 4e1041dc39 (unit-tests: show location of checks\n  outside of tests) from seen, but with a test,\n- document incompatibility of for_test and break,\n- add blank line between variable declarations and rest of strbuf test\n  code to match the original style.\n\n  t0080: use here-doc test body\n  unit-tests: show location of checks outside of tests\n  unit-tests: add for_test\n  t-ctype: use for_test\n  t-reftable-basics: use for_test\n  t-strvec: use for_test\n  t-strbuf: use for_test\n\n .clang-format                    |   2 +\n t/helper/test-example-tap.c      |  35 +++\n t/t0080-unit-test-output.sh      |  60 ++++--\n t/unit-tests/t-ctype.c           |   4 +-\n t/unit-tests/t-reftable-basics.c | 228 +++++++++-----------\n t/unit-tests/t-strbuf.c          | 127 +++++------\n t/unit-tests/t-strvec.c          | 356 ++++++++++++++-----------------\n t/unit-tests/test-lib.c          |   7 +-\n t/unit-tests/test-lib.h          |  20 ++\n 9 files changed, 438 insertions(+), 401 deletions(-)\n\nRange-Diff gegen v2:\n1:  5faabaea54 < -:  ---------- t0080: move expected output to a file\n-:  ---------- > 1:  9b919853df t0080: use here-doc test body\n-:  ---------- > 2:  9cea2b43b9 unit-tests: show location of checks outside of tests\n2:  d4f9fa0938 ! 3:  5ea7472d8a unit-tests: add for_test\n    @@ t/helper/test-example-tap.c: int cmd__example_tap(int argc, const char **argv)\n      \treturn test_done();\n      }\n\n    - ## t/t0080/expect ##\n    -@@ t/t0080/expect: not ok 17 - messages from failing string and char comparison\n    - # BUG: test has no checks at t/helper/test-example-tap.c:92\n    - not ok 18 - test with no checks\n    - ok 19 - test with no checks returns 0\n    --1..19\n    -+ok 20 - for_test passing test\n    -+# check \"1 == 2\" failed at t/helper/test-example-tap.c:98\n    -+#    left: 1\n    -+#   right: 2\n    -+not ok 21 - for_test failing test\n    -+not ok 22 - for_test passing TEST_TODO() # TODO\n    -+# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:102\n    -+not ok 23 - for_test failing TEST_TODO()\n    -+# check \"0\" failed at t/helper/test-example-tap.c:104\n    -+# skipping test - missing prerequisite\n    -+# skipping check '1' at t/helper/test-example-tap.c:106\n    -+ok 24 - for_test test_skip() # SKIP\n    -+# skipping test - missing prerequisite\n    -+ok 25 - for_test test_skip() inside TEST_TODO() # SKIP\n    -+# check \"0\" failed at t/helper/test-example-tap.c:111\n    -+not ok 26 - for_test TEST_TODO() after failing check\n    -+# check \"0\" failed at t/helper/test-example-tap.c:117\n    -+not ok 27 - for_test failing check after TEST_TODO()\n    -+# check \"!strcmp(\"\\thello\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:120\n    -+#    left: \"\\011hello\\\\\"\n    -+#   right: \"there\\\"\\012\"\n    -+# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:121\n    -+#    left: \"NULL\"\n    -+#   right: NULL\n    -+# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:122\n    -+#    left: 'a'\n    -+#   right: '\\012'\n    -+# check \"'\\\\' == '\\''\" failed at t/helper/test-example-tap.c:123\n    -+#    left: '\\\\'\n    -+#   right: '\\''\n    -+not ok 28 - for_test messages from failing string and char comparison\n    -+# BUG: test has no checks at t/helper/test-example-tap.c:125\n    -+not ok 29 - for_test test with no checks\n    -+1..29\n    + ## t/t0080-unit-test-output.sh ##\n    +@@ t/t0080-unit-test-output.sh: test_expect_success 'TAP output from unit tests' - <<\\EOT\n    + \t# BUG: test has no checks at t/helper/test-example-tap.c:94\n    + \tnot ok 18 - test with no checks\n    + \tok 19 - test with no checks returns 0\n    +-\t1..19\n    ++\tok 20 - for_test passing test\n    ++\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:100\n    ++\t#    left: 1\n    ++\t#   right: 2\n    ++\tnot ok 21 - for_test failing test\n    ++\tnot ok 22 - for_test passing TEST_TODO() # TODO\n    ++\t# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104\n    ++\tnot ok 23 - for_test failing TEST_TODO()\n    ++\t# check \"0\" failed at t/helper/test-example-tap.c:106\n    ++\t# skipping test - missing prerequisite\n    ++\t# skipping check '1' at t/helper/test-example-tap.c:108\n    ++\tok 24 - for_test test_skip() # SKIP\n    ++\t# skipping test - missing prerequisite\n    ++\tok 25 - for_test test_skip() inside TEST_TODO() # SKIP\n    ++\t# check \"0\" failed at t/helper/test-example-tap.c:113\n    ++\tnot ok 26 - for_test TEST_TODO() after failing check\n    ++\t# check \"0\" failed at t/helper/test-example-tap.c:119\n    ++\tnot ok 27 - for_test failing check after TEST_TODO()\n    ++\t# check \"!strcmp(\"\\thello\\\\\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:122\n    ++\t#    left: \"\\011hello\\\\\\\\\"\n    ++\t#   right: \"there\\\"\\012\"\n    ++\t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:123\n    ++\t#    left: \"NULL\"\n    ++\t#   right: NULL\n    ++\t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:124\n    ++\t#    left: 'a'\n    ++\t#   right: '\\012'\n    ++\t# check \"'\\\\\\\\' == '\\\\''\" failed at t/helper/test-example-tap.c:125\n    ++\t#    left: '\\\\\\\\'\n    ++\t#   right: '\\\\''\n    ++\tnot ok 28 - for_test messages from failing string and char comparison\n    ++\t# BUG: test has no checks at t/helper/test-example-tap.c:127\n    ++\tnot ok 29 - for_test test with no checks\n    ++\t1..29\n    + \tEOF\n    +\n    + \t! test-tool example-tap >actual &&\n\n      ## t/unit-tests/test-lib.h ##\n     @@\n    @@ t/unit-tests/test-lib.h\n     + * Run a test unless test_skip_all() has been called.  Acts like a for\n     + * loop that runs at most once, with the test description between the\n     + * parentheses and the test body as a statement or block after them.\n    -+ * The description for each test should be unique.  E.g.:\n    ++ * Supports continue to end the test early, but not break. The\n    ++ * description for each test should be unique.  E.g.:\n     + *\n     + *  for_test (\"something else %d %d\", arg1, arg2) {\n     + *          prepare();\n3:  a7cd5a2a3a = 4:  cf2fa74e9c t-ctype: use for_test\n4:  cc07910f88 = 5:  f2e6271124 t-reftable-basics: use for_test\n5:  11c1675a13 = 6:  c87a17189c t-strvec: use for_test\n6:  cd79132f95 ! 7:  beaf8194fd t-strbuf: use for_test\n    @@ t/unit-tests/t-strbuf.c: static void t_addstr(struct strbuf *buf, const void *da\n     +\n     +\tfor_test (\"strbuf_addch adds char\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n    ++\n     +\t\tt_addch(&sb, 'a');\n     +\t\tt_release(&sb);\n     +\t}\n     +\n     +\tfor_test (\"strbuf_addch adds NUL char\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n    ++\n     +\t\tt_addch(&sb, '\\0');\n     +\t\tt_release(&sb);\n     +\t}\n     +\n     +\tfor_test (\"strbuf_addch appends to initial value\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n    ++\n     +\t\tt_addstr(&sb, \"initial value\");\n     +\t\tt_addch(&sb, 'a');\n     +\t\tt_release(&sb);\n    @@ t/unit-tests/t-strbuf.c: static void t_addstr(struct strbuf *buf, const void *da\n     +\n     +\tfor_test (\"strbuf_addstr adds string\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n    ++\n     +\t\tt_addstr(&sb, \"hello there\");\n     +\t\tt_release(&sb);\n     +\t}\n     +\n     +\tfor_test (\"strbuf_addstr appends string to initial value\") {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n    ++\n     +\t\tt_addstr(&sb, \"initial value\");\n     +\t\tt_addstr(&sb, \"hello there\");\n     +\t\tt_release(&sb);\n7:  83dafb009d < -:  ---------- unit-tests: show location of checks outside of tests\n--\n2.45.2\n"},{"id":"499261","messageId":"33073397-cd86-49ea-a5fd-d045dc98725a@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 1/7] t0080: use here-doc test body","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:48:01Z","receivedAt":"2024-07-24T14:48:24Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Improve the readability of the expected output by using a here-doc for\nthe test body and replacing the unwieldy ${SQ} references with literal\nsingle quotes.\n\nSuggested-by: Jeff King <peff@peff.net>\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/t0080-unit-test-output.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex 7bbb065d58..9ec47b7360 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -5,7 +5,7 @@ test_description='Test the output of the unit test framework'\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n\n-test_expect_success 'TAP output from unit tests' '\n+test_expect_success 'TAP output from unit tests' - <<\\EOT\n \tcat >expect <<-EOF &&\n \tok 1 - passing test\n \tok 2 - passing test and assertion return 1\n@@ -16,12 +16,12 @@ test_expect_success 'TAP output from unit tests' '\n \tok 4 - failing test and assertion return 0\n \tnot ok 5 - passing TEST_TODO() # TODO\n \tok 6 - passing TEST_TODO() returns 1\n-\t# todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26\n+\t# todo check 'check(x)' succeeded at t/helper/test-example-tap.c:26\n \tnot ok 7 - failing TEST_TODO()\n \tok 8 - failing TEST_TODO() returns 0\n \t# check \"0\" failed at t/helper/test-example-tap.c:31\n \t# skipping test - missing prerequisite\n-\t# skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33\n+\t# skipping check '1' at t/helper/test-example-tap.c:33\n \tok 9 - test_skip() # SKIP\n \tok 10 - skipped test returns 1\n \t# skipping test - missing prerequisite\n@@ -39,12 +39,12 @@ test_expect_success 'TAP output from unit tests' '\n \t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:63\n \t#    left: \"NULL\"\n \t#   right: NULL\n-\t# check \"${SQ}a${SQ} == ${SQ}\\n${SQ}\" failed at t/helper/test-example-tap.c:64\n-\t#    left: ${SQ}a${SQ}\n-\t#   right: ${SQ}\\012${SQ}\n-\t# check \"${SQ}\\\\\\\\${SQ} == ${SQ}\\\\${SQ}${SQ}\" failed at t/helper/test-example-tap.c:65\n-\t#    left: ${SQ}\\\\\\\\${SQ}\n-\t#   right: ${SQ}\\\\${SQ}${SQ}\n+\t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n+\t#    left: 'a'\n+\t#   right: '\\012'\n+\t# check \"'\\\\\\\\' == '\\\\''\" failed at t/helper/test-example-tap.c:65\n+\t#    left: '\\\\\\\\'\n+\t#   right: '\\\\''\n \tnot ok 17 - messages from failing string and char comparison\n \t# BUG: test has no checks at t/helper/test-example-tap.c:92\n \tnot ok 18 - test with no checks\n@@ -54,6 +54,6 @@ test_expect_success 'TAP output from unit tests' '\n\n \t! test-tool example-tap >actual &&\n \ttest_cmp expect actual\n-'\n+EOT\n\n test_done\n--\n2.45.2\n"},{"id":"499266","messageId":"92741509-106a-4eba-b662-74b50f7f859f@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 2/7] unit-tests: show location of checks outside of tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:50:58Z","receivedAt":"2024-07-24T14:51:11Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Checks outside of tests are caught at runtime and reported like this:\n\n Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n\nThe assert() call aborts the unit test and doesn't reveal the location\nor even the type of the offending check, as test_assert() is called by\nall of them.\n\nHandle it like the opposite case, a test without any checks: Don't\nabort, but report the location of the actual check, along with a message\nexplaining the situation.  The output for example above becomes:\n\n # BUG: check outside of test at t/helper/test-example-tap.c:75\n\n... and the unit test program continues and indicates the error in its\nexit code at the end.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\nSupersedes 9cea2b43b9 (unit-tests: show location of checks outside of\ntests, 2024-07-22) in seen.  Changes: Got a test.\n\n t/helper/test-example-tap.c | 2 ++\n t/t0080-unit-test-output.sh | 5 +++--\n t/unit-tests/test-lib.c     | 7 ++++++-\n 3 files changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\nindex d072ad559f..79c12b01cd 100644\n--- a/t/helper/test-example-tap.c\n+++ b/t/helper/test-example-tap.c\n@@ -72,6 +72,8 @@ static void t_empty(void)\n\n int cmd__example_tap(int argc, const char **argv)\n {\n+\tcheck(1);\n+\n \ttest_res = TEST(check_res = check_int(1, ==, 1), \"passing test\");\n \tTEST(t_res(1), \"passing test and assertion return 1\");\n \ttest_res = TEST(check_res = check_int(1, ==, 2), \"failing test\");\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex 9ec47b7360..fe221f3bdb 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -7,9 +7,10 @@ TEST_PASSES_SANITIZE_LEAK=true\n\n test_expect_success 'TAP output from unit tests' - <<\\EOT\n \tcat >expect <<-EOF &&\n+\t# BUG: check outside of test at t/helper/test-example-tap.c:75\n \tok 1 - passing test\n \tok 2 - passing test and assertion return 1\n-\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n+\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:79\n \t#    left: 1\n \t#   right: 2\n \tnot ok 3 - failing test\n@@ -46,7 +47,7 @@ test_expect_success 'TAP output from unit tests' - <<\\EOT\n \t#    left: '\\\\\\\\'\n \t#   right: '\\\\''\n \tnot ok 17 - messages from failing string and char comparison\n-\t# BUG: test has no checks at t/helper/test-example-tap.c:92\n+\t# BUG: test has no checks at t/helper/test-example-tap.c:94\n \tnot ok 18 - test with no checks\n \tok 19 - test with no checks returns 0\n \t1..19\ndiff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\nindex 3c513ce59a..989dc758e6 100644\n--- a/t/unit-tests/test-lib.c\n+++ b/t/unit-tests/test-lib.c\n@@ -264,7 +264,12 @@ static void test_todo(void)\n\n int test_assert(const char *location, const char *check, int ok)\n {\n-\tassert(ctx.running);\n+\tif (!ctx.running) {\n+\t\ttest_msg(\"BUG: check outside of test at %s\",\n+\t\t\t make_relative(location));\n+\t\tctx.failed = 1;\n+\t\treturn 0;\n+\t}\n\n \tif (ctx.result == RESULT_SKIP) {\n \t\ttest_msg(\"skipping check '%s' at %s\", check,\n--\n2.45.2\n"},{"id":"499267","messageId":"c51025cc-26e5-41e2-be56-94e141a64f5d@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 3/7] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:51:51Z","receivedAt":"2024-07-24T14:52:02Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST only allows defining a test that consists of a single\nexpression.  Add a new macro, for_test, which provides a way to define\nunit tests that are made up of one or more statements.\n\nfor_test allows defining self-contained tests en bloc, a bit like\ntest_expect_success does for regular tests.  It acts like a for loop\nthat runs at most once; the test body is executed if test_skip_all()\nhad not been called before.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n .clang-format               |  2 ++\n t/helper/test-example-tap.c | 33 +++++++++++++++++++++++++++++++++\n t/t0080-unit-test-output.sh | 35 ++++++++++++++++++++++++++++++++++-\n t/unit-tests/test-lib.h     | 20 ++++++++++++++++++++\n 4 files changed, 89 insertions(+), 1 deletion(-)\n\ndiff --git a/.clang-format b/.clang-format\nindex 6408251577..863dc87dfc 100644\n--- a/.clang-format\n+++ b/.clang-format\n@@ -151,6 +151,7 @@ Cpp11BracedListStyle: false\n # function calls. Taken from:\n #   git grep -h '^#define [^[:space:]]*for_\\?each[^[:space:]]*(' |\n #   sed \"s/^#define /  - '/; s/(.*$/'/\" | sort | uniq\n+# Added for_test from t/unit-tests/test-lib.h manually as a special case.\n ForEachMacros:\n   - 'for_each_builtin'\n   - 'for_each_string_list_item'\n@@ -168,6 +169,7 @@ ForEachMacros:\n   - 'strintmap_for_each_entry'\n   - 'strmap_for_each_entry'\n   - 'strset_for_each_entry'\n+  - 'for_test'\n\n # The maximum number of consecutive empty lines to keep.\n MaxEmptyLinesToKeep: 1\ndiff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\nindex 79c12b01cd..5e49fb1e7e 100644\n--- a/t/helper/test-example-tap.c\n+++ b/t/helper/test-example-tap.c\n@@ -94,5 +94,38 @@ int cmd__example_tap(int argc, const char **argv)\n \ttest_res = TEST(t_empty(), \"test with no checks\");\n \tTEST(check_int(test_res, ==, 0), \"test with no checks returns 0\");\n\n+\tfor_test (\"for_test passing test\")\n+\t\tcheck_int(1, ==, 1);\n+\tfor_test (\"for_test failing test\")\n+\t\tcheck_int(1, ==, 2);\n+\tfor_test (\"for_test passing TEST_TODO()\")\n+\t\tTEST_TODO(check(0));\n+\tfor_test (\"for_test failing TEST_TODO()\")\n+\t\tTEST_TODO(check(1));\n+\tfor_test (\"for_test test_skip()\") {\n+\t\tcheck(0);\n+\t\ttest_skip(\"missing prerequisite\");\n+\t\tcheck(1);\n+\t}\n+\tfor_test (\"for_test test_skip() inside TEST_TODO()\")\n+\t\tTEST_TODO((test_skip(\"missing prerequisite\"), 1));\n+\tfor_test (\"for_test TEST_TODO() after failing check\") {\n+\t\tcheck(0);\n+\t\tTEST_TODO(check(0));\n+\t}\n+\tfor_test (\"for_test failing check after TEST_TODO()\") {\n+\t\tcheck(1);\n+\t\tTEST_TODO(check(0));\n+\t\tcheck(0);\n+\t}\n+\tfor_test (\"for_test messages from failing string and char comparison\") {\n+\t\tcheck_str(\"\\thello\\\\\", \"there\\\"\\n\");\n+\t\tcheck_str(\"NULL\", NULL);\n+\t\tcheck_char('a', ==, '\\n');\n+\t\tcheck_char('\\\\', ==, '\\'');\n+\t}\n+\tfor_test (\"for_test test with no checks\")\n+\t\t; /* nothing */\n+\n \treturn test_done();\n }\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex fe221f3bdb..5185154414 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -50,7 +50,40 @@ test_expect_success 'TAP output from unit tests' - <<\\EOT\n \t# BUG: test has no checks at t/helper/test-example-tap.c:94\n \tnot ok 18 - test with no checks\n \tok 19 - test with no checks returns 0\n-\t1..19\n+\tok 20 - for_test passing test\n+\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:100\n+\t#    left: 1\n+\t#   right: 2\n+\tnot ok 21 - for_test failing test\n+\tnot ok 22 - for_test passing TEST_TODO() # TODO\n+\t# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104\n+\tnot ok 23 - for_test failing TEST_TODO()\n+\t# check \"0\" failed at t/helper/test-example-tap.c:106\n+\t# skipping test - missing prerequisite\n+\t# skipping check '1' at t/helper/test-example-tap.c:108\n+\tok 24 - for_test test_skip() # SKIP\n+\t# skipping test - missing prerequisite\n+\tok 25 - for_test test_skip() inside TEST_TODO() # SKIP\n+\t# check \"0\" failed at t/helper/test-example-tap.c:113\n+\tnot ok 26 - for_test TEST_TODO() after failing check\n+\t# check \"0\" failed at t/helper/test-example-tap.c:119\n+\tnot ok 27 - for_test failing check after TEST_TODO()\n+\t# check \"!strcmp(\"\\thello\\\\\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:122\n+\t#    left: \"\\011hello\\\\\\\\\"\n+\t#   right: \"there\\\"\\012\"\n+\t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:123\n+\t#    left: \"NULL\"\n+\t#   right: NULL\n+\t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:124\n+\t#    left: 'a'\n+\t#   right: '\\012'\n+\t# check \"'\\\\\\\\' == '\\\\''\" failed at t/helper/test-example-tap.c:125\n+\t#    left: '\\\\\\\\'\n+\t#   right: '\\\\''\n+\tnot ok 28 - for_test messages from failing string and char comparison\n+\t# BUG: test has no checks at t/helper/test-example-tap.c:127\n+\tnot ok 29 - for_test test with no checks\n+\t1..29\n \tEOF\n\n \t! test-tool example-tap >actual &&\ndiff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h\nindex 2de6d715d5..598c6ff9f3 100644\n--- a/t/unit-tests/test-lib.h\n+++ b/t/unit-tests/test-lib.h\n@@ -14,6 +14,26 @@\n \ttest__run_end(test__run_begin() ? 0 : (t, 1),\t\\\n \t\t      TEST_LOCATION(),  __VA_ARGS__)\n\n+/*\n+ * Run a test unless test_skip_all() has been called.  Acts like a for\n+ * loop that runs at most once, with the test description between the\n+ * parentheses and the test body as a statement or block after them.\n+ * Supports continue to end the test early, but not break. The\n+ * description for each test should be unique.  E.g.:\n+ *\n+ *  for_test (\"something else %d %d\", arg1, arg2) {\n+ *          prepare();\n+ *          test_something_else(arg1, arg2);\n+ *          cleanup();\n+ *  }\n+ */\n+#define for_test(...)\t\t\t\t\t\t\t\\\n+\tfor (int for_test_running_ = test__run_begin() ?\t\t\\\n+\t\t(test__run_end(0, TEST_LOCATION(), __VA_ARGS__), 0) : 1;\\\n+\t     for_test_running_;\t\t\t\t\t\t\\\n+\t     test__run_end(1, TEST_LOCATION(), __VA_ARGS__),\t\t\\\n+\t\tfor_test_running_ = 0)\n+\n /*\n  * Print a test plan, should be called before any tests. If the number\n  * of tests is not known in advance test_done() will automatically\n--\n2.45.2\n"},{"id":"499268","messageId":"0fa8b2fc-ac59-45da-abcc-1ac758e4813e@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 4/7] t-ctype: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:52:56Z","receivedAt":"2024-07-24T14:53:09Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Use the documented macro for_test instead of the internal functions\ntest__run_begin() and test__run_end(), which are supposed to be private\nto the unit test framework.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-ctype.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c\nindex d6ac1fe678..92a05f02b3 100644\n--- a/t/unit-tests/t-ctype.c\n+++ b/t/unit-tests/t-ctype.c\n@@ -4,15 +4,13 @@\n \tsize_t len = ARRAY_SIZE(string) - 1 + \\\n \t\tBUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \\\n \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n-\tint skip = test__run_begin(); \\\n-\tif (!skip) { \\\n+\tfor_test (#class \" works\") { \\\n \t\tfor (int i = 0; i < 256; i++) { \\\n \t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n \t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n \t\t} \\\n \t\tcheck(!class(EOF)); \\\n \t} \\\n-\ttest__run_end(!skip, TEST_LOCATION(), #class \" works\"); \\\n } while (0)\n\n #define DIGIT \"0123456789\"\n--\n2.45.2\n"},{"id":"499269","messageId":"e0485dd4-5428-4b92-8d3d-3cf6cfe6a53c@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 5/7] t-reftable-basics: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:54:03Z","receivedAt":"2024-07-24T14:54:14Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the overhead of defining and calling single-use functions by\nusing for_test instead.\n\nRun the tests in the order of definition.  We can reorder them like that\nbecause they are independent.  Technically this changes the output, but\nretains the meaning of a full run and allows for easier review e.g. with\ndiff option --ignore-all-space.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-reftable-basics.c | 228 ++++++++++++++-----------------\n 1 file changed, 106 insertions(+), 122 deletions(-)\n\ndiff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c\nindex 4e80bdf16d..eb7af9ade4 100644\n--- a/t/unit-tests/t-reftable-basics.c\n+++ b/t/unit-tests/t-reftable-basics.c\n@@ -20,141 +20,125 @@ static int integer_needle_lesseq(size_t i, void *_args)\n \treturn args->needle <= args->haystack[i];\n }\n\n-static void test_binsearch(void)\n+int cmd_main(int argc, const char *argv[])\n {\n-\tint haystack[] = { 2, 4, 6, 8, 10 };\n-\tstruct {\n-\t\tint needle;\n-\t\tsize_t expected_idx;\n-\t} testcases[] = {\n-\t\t{-9000, 0},\n-\t\t{-1, 0},\n-\t\t{0, 0},\n-\t\t{2, 0},\n-\t\t{3, 1},\n-\t\t{4, 1},\n-\t\t{7, 3},\n-\t\t{9, 4},\n-\t\t{10, 4},\n-\t\t{11, 5},\n-\t\t{9000, 5},\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n-\t\tstruct integer_needle_lesseq_args args = {\n-\t\t\t.haystack = haystack,\n-\t\t\t.needle = testcases[i].needle,\n+\tfor_test (\"binary search with binsearch works\") {\n+\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n+\t\tstruct {\n+\t\t\tint needle;\n+\t\t\tsize_t expected_idx;\n+\t\t} testcases[] = {\n+\t\t\t{-9000, 0},\n+\t\t\t{-1, 0},\n+\t\t\t{0, 0},\n+\t\t\t{2, 0},\n+\t\t\t{3, 1},\n+\t\t\t{4, 1},\n+\t\t\t{7, 3},\n+\t\t\t{9, 4},\n+\t\t\t{10, 4},\n+\t\t\t{11, 5},\n+\t\t\t{9000, 5},\n \t\t};\n-\t\tsize_t idx;\n\n-\t\tidx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args);\n-\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n+\t\t\tstruct integer_needle_lesseq_args args = {\n+\t\t\t\t.haystack = haystack,\n+\t\t\t\t.needle = testcases[i].needle,\n+\t\t\t};\n+\t\t\tsize_t idx;\n+\n+\t\t\tidx = binsearch(ARRAY_SIZE(haystack),\n+\t\t\t\t\t&integer_needle_lesseq, &args);\n+\t\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\t}\n \t}\n-}\n\n-static void test_names_length(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", NULL };\n-\tcheck_int(names_length(a), ==, 2);\n-}\n-\n-static void test_names_equal(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tconst char *c[] = { \"a\", \"b\", NULL };\n+\tfor_test (\"names_length retuns size of a NULL-terminated string array\") {\n+\t\tconst char *a[] = { \"a\", \"b\", NULL };\n+\t\tcheck_int(names_length(a), ==, 2);\n+\t}\n\n-\tcheck(names_equal(a, a));\n-\tcheck(!names_equal(a, b));\n-\tcheck(!names_equal(a, c));\n-}\n+\tfor_test (\"names_equal compares NULL-terminated string arrays\") {\n+\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\t\tconst char *c[] = { \"a\", \"b\", NULL };\n\n-static void test_parse_names_normal(void)\n-{\n-\tchar in1[] = \"line\\n\";\n-\tchar in2[] = \"a\\nb\\nc\";\n-\tchar **out = NULL;\n-\tparse_names(in1, strlen(in1), &out);\n-\tcheck_str(out[0], \"line\");\n-\tcheck(!out[1]);\n-\tfree_names(out);\n-\n-\tparse_names(in2, strlen(in2), &out);\n-\tcheck_str(out[0], \"a\");\n-\tcheck_str(out[1], \"b\");\n-\tcheck_str(out[2], \"c\");\n-\tcheck(!out[3]);\n-\tfree_names(out);\n-}\n+\t\tcheck(names_equal(a, a));\n+\t\tcheck(!names_equal(a, b));\n+\t\tcheck(!names_equal(a, c));\n+\t}\n\n-static void test_parse_names_drop_empty(void)\n-{\n-\tchar in[] = \"a\\n\\nb\\n\";\n-\tchar **out = NULL;\n-\tparse_names(in, strlen(in), &out);\n-\tcheck_str(out[0], \"a\");\n-\t/* simply '\\n' should be dropped as empty string */\n-\tcheck_str(out[1], \"b\");\n-\tcheck(!out[2]);\n-\tfree_names(out);\n-}\n+\tfor_test (\"parse_names works for basic input\") {\n+\t\tchar in1[] = \"line\\n\";\n+\t\tchar in2[] = \"a\\nb\\nc\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in1, strlen(in1), &out);\n+\t\tcheck_str(out[0], \"line\");\n+\t\tcheck(!out[1]);\n+\t\tfree_names(out);\n+\n+\t\tparse_names(in2, strlen(in2), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck_str(out[2], \"c\");\n+\t\tcheck(!out[3]);\n+\t\tfree_names(out);\n+\t}\n\n-static void test_common_prefix(void)\n-{\n-\tstruct strbuf a = STRBUF_INIT;\n-\tstruct strbuf b = STRBUF_INIT;\n-\tstruct {\n-\t\tconst char *a, *b;\n-\t\tint want;\n-\t} cases[] = {\n-\t\t{\"abcdef\", \"abc\", 3},\n-\t\t{ \"abc\", \"ab\", 2 },\n-\t\t{ \"\", \"abc\", 0 },\n-\t\t{ \"abc\", \"abd\", 2 },\n-\t\t{ \"abc\", \"pqr\", 0 },\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n-\t\tstrbuf_addstr(&a, cases[i].a);\n-\t\tstrbuf_addstr(&b, cases[i].b);\n-\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n-\t\tstrbuf_reset(&a);\n-\t\tstrbuf_reset(&b);\n+\tfor_test (\"parse_names drops empty string\") {\n+\t\tchar in[] = \"a\\n\\nb\\n\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in, strlen(in), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\t/* simply '\\n' should be dropped as empty string */\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck(!out[2]);\n+\t\tfree_names(out);\n \t}\n-\tstrbuf_release(&a);\n-\tstrbuf_release(&b);\n-}\n\n-static void test_u24_roundtrip(void)\n-{\n-\tuint32_t in = 0x112233;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be24(dest, in);\n-\tout = get_be24(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\tfor_test (\"common_prefix_size works\") {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstruct {\n+\t\t\tconst char *a, *b;\n+\t\t\tint want;\n+\t\t} cases[] = {\n+\t\t\t{\"abcdef\", \"abc\", 3},\n+\t\t\t{ \"abc\", \"ab\", 2 },\n+\t\t\t{ \"\", \"abc\", 0 },\n+\t\t\t{ \"abc\", \"abd\", 2 },\n+\t\t\t{ \"abc\", \"pqr\", 0 },\n+\t\t};\n\n-static void test_u16_roundtrip(void)\n-{\n-\tuint32_t in = 0xfef1;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be16(dest, in);\n-\tout = get_be16(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n+\t\t\tstrbuf_reset(&a);\n+\t\t\tstrbuf_reset(&b);\n+\t\t}\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n\n-int cmd_main(int argc, const char *argv[])\n-{\n-\tTEST(test_common_prefix(), \"common_prefix_size works\");\n-\tTEST(test_parse_names_normal(), \"parse_names works for basic input\");\n-\tTEST(test_parse_names_drop_empty(), \"parse_names drops empty string\");\n-\tTEST(test_binsearch(), \"binary search with binsearch works\");\n-\tTEST(test_names_length(), \"names_length retuns size of a NULL-terminated string array\");\n-\tTEST(test_names_equal(), \"names_equal compares NULL-terminated string arrays\");\n-\tTEST(test_u24_roundtrip(), \"put_be24 and get_be24 work\");\n-\tTEST(test_u16_roundtrip(), \"put_be16 and get_be16 work\");\n+\tfor_test (\"put_be24 and get_be24 work\") {\n+\t\tuint32_t in = 0x112233;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be24(dest, in);\n+\t\tout = get_be24(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n+\n+\tfor_test (\"put_be16 and get_be16 work\") {\n+\t\tuint32_t in = 0xfef1;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be16(dest, in);\n+\t\tout = get_be16(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"499270","messageId":"89c04cd4-11da-46cf-a061-b2e4ba09b99e@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 6/7] t-strvec: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:54:52Z","receivedAt":"2024-07-24T14:55:02Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the cognitive overhead of defining and calling single-use\nfunctions by using for_test instead.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strvec.c | 356 ++++++++++++++++++----------------------\n 1 file changed, 156 insertions(+), 200 deletions(-)\n\ndiff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c\nindex d4615ab06d..4fa2c21afb 100644\n--- a/t/unit-tests/t-strvec.c\n+++ b/t/unit-tests/t-strvec.c\n@@ -36,237 +36,193 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n \tcheck_pointer_eq(vec->v[nr], NULL);\n }\n\n-static void t_static_init(void)\n+int cmd_main(int argc, const char **argv)\n {\n-\tstruct strvec vec = STRVEC_INIT;\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tfor_test (\"static initialization\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_dynamic_init(void)\n-{\n-\tstruct strvec vec;\n-\tstrvec_init(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tfor_test (\"dynamic initialization\") {\n+\t\tstruct strvec vec;\n+\t\tstrvec_init(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_clear(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_push(&vec, \"foo\");\n-\tstrvec_clear(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tfor_test (\"clear\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_clear(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_push(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n+\tfor_test (\"push\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_push(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n\n-\tstrvec_push(&vec, \"bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_push(&vec, \"bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushf(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushf(&vec, \"foo: %d\", 1);\n-\tcheck_strvec(&vec, \"foo: 1\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pushf\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n+\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushl(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pushl\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushv(void)\n-{\n-\tconst char *strings[] = {\n-\t\t\"foo\", \"bar\", \"baz\", NULL,\n-\t};\n-\tstruct strvec vec = STRVEC_INIT;\n+\tfor_test (\"pushv\") {\n+\t\tconst char *strings[] = {\n+\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n+\t\t};\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_pushv(&vec, strings);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pushv(&vec, strings);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 0, \"replaced\");\n-\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace at head\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 0, \"replaced\");\n+\t\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 2, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace at tail\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 2, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 1, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace in between\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 1, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_with_substring(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", NULL);\n-\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n-\tcheck_strvec(&vec, \"oo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"replace with substring\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", NULL);\n+\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n+\t\tcheck_strvec(&vec, \"oo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 0);\n-\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"remove at head\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 0);\n+\t\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 2);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"remove at tail\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 2);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 1);\n-\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"remove in between\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 1);\n+\t\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pop with empty array\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_non_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"pop with non-empty array\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_empty_string(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split empty string\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_single_item(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split single item\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_items(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo bar baz\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split multiple items\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo bar baz\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_whitespace_only(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \" \\t\\n\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split whitespace only\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \" \\t\\n\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_consecutive_whitespaces(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\\n\\t bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tfor_test (\"split multiple consecutive whitespaces\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_detach(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tconst char **detached;\n+\tfor_test (\"detach\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tconst char **detached;\n\n-\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_push(&vec, \"foo\");\n\n-\tdetached = strvec_detach(&vec);\n-\tcheck_str(detached[0], \"foo\");\n-\tcheck_pointer_eq(detached[1], NULL);\n+\t\tdetached = strvec_detach(&vec);\n+\t\tcheck_str(detached[0], \"foo\");\n+\t\tcheck_pointer_eq(detached[1], NULL);\n\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n\n-\tfree((char *) detached[0]);\n-\tfree(detached);\n-}\n+\t\tfree((char *) detached[0]);\n+\t\tfree(detached);\n+\t}\n\n-int cmd_main(int argc, const char **argv)\n-{\n-\tTEST(t_static_init(), \"static initialization\");\n-\tTEST(t_dynamic_init(), \"dynamic initialization\");\n-\tTEST(t_clear(), \"clear\");\n-\tTEST(t_push(), \"push\");\n-\tTEST(t_pushf(), \"pushf\");\n-\tTEST(t_pushl(), \"pushl\");\n-\tTEST(t_pushv(), \"pushv\");\n-\tTEST(t_replace_at_head(), \"replace at head\");\n-\tTEST(t_replace_in_between(), \"replace in between\");\n-\tTEST(t_replace_at_tail(), \"replace at tail\");\n-\tTEST(t_replace_with_substring(), \"replace with substring\");\n-\tTEST(t_remove_at_head(), \"remove at head\");\n-\tTEST(t_remove_in_between(), \"remove in between\");\n-\tTEST(t_remove_at_tail(), \"remove at tail\");\n-\tTEST(t_pop_empty_array(), \"pop with empty array\");\n-\tTEST(t_pop_non_empty_array(), \"pop with non-empty array\");\n-\tTEST(t_split_empty_string(), \"split empty string\");\n-\tTEST(t_split_single_item(), \"split single item\");\n-\tTEST(t_split_multiple_items(), \"split multiple items\");\n-\tTEST(t_split_whitespace_only(), \"split whitespace only\");\n-\tTEST(t_split_multiple_consecutive_whitespaces(), \"split multiple consecutive whitespaces\");\n-\tTEST(t_detach(), \"detach\");\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"499271","messageId":"a899a8be-3d14-4b93-ac38-ccb2a5ef1e22@web.de","threadId":"61700","inReplyTo":"73465c3d-1be0-456b-9471-f875e819c566@web.de","subject":"[PATCH v3 7/7] t-strbuf: use for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-24T14:55:42Z","receivedAt":"2024-07-24T14:55:52Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.  The functions setup() and setup_populated() here\nare used for that purpose and take another function as an argument,\nmaking the control flow hard to follow.\n\nRemove the overhead of these functions by using for_test instead.  Move\ntheir duplicate post-condition checks into a new helper, t_release(),\nand let t_addch() and t_addstr() accept properly typed input parameters\ninstead of void pointers.\n\nUse the fully checking t_addstr() for adding initial values instead of\nonly doing only a length comparison -- there's no need for skipping the\nother checks.\n\nThis results in test cases that look much more like strbuf usage in\nproduction code, only with checked strbuf functions replaced by checking\nwrappers.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strbuf.c | 127 ++++++++++++++++++++--------------------\n 1 file changed, 65 insertions(+), 62 deletions(-)\n\ndiff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c\nindex 6027dafef7..1e4a7885c9 100644\n--- a/t/unit-tests/t-strbuf.c\n+++ b/t/unit-tests/t-strbuf.c\n@@ -1,32 +1,6 @@\n #include \"test-lib.h\"\n #include \"strbuf.h\"\n\n-/* wrapper that supplies tests with an empty, initialized strbuf */\n-static void setup(void (*f)(struct strbuf*, const void*),\n-\t\t  const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n-/* wrapper that supplies tests with a populated, initialized strbuf */\n-static void setup_populated(void (*f)(struct strbuf*, const void*),\n-\t\t\t    const char *init_str, const void *data)\n-{\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tstrbuf_addstr(&buf, init_str);\n-\tcheck_uint(buf.len, ==, strlen(init_str));\n-\tf(&buf, data);\n-\tstrbuf_release(&buf);\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-}\n-\n static int assert_sane_strbuf(struct strbuf *buf)\n {\n \t/* Initialized strbufs should always have a non-NULL buffer */\n@@ -45,31 +19,8 @@ static int assert_sane_strbuf(struct strbuf *buf)\n \treturn check_uint(buf->len, <, buf->alloc);\n }\n\n-static void t_static_init(void)\n+static void t_addch(struct strbuf *buf, int ch)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n-\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, ==, 0);\n-\tcheck_char(buf.buf[0], ==, '\\0');\n-}\n-\n-static void t_dynamic_init(void)\n-{\n-\tstruct strbuf buf;\n-\n-\tstrbuf_init(&buf, 1024);\n-\tcheck(assert_sane_strbuf(&buf));\n-\tcheck_uint(buf.len, ==, 0);\n-\tcheck_uint(buf.alloc, >=, 1024);\n-\tcheck_char(buf.buf[0], ==, '\\0');\n-\tstrbuf_release(&buf);\n-}\n-\n-static void t_addch(struct strbuf *buf, const void *data)\n-{\n-\tconst char *p_ch = data;\n-\tconst char ch = *p_ch;\n \tsize_t orig_alloc = buf->alloc;\n \tsize_t orig_len = buf->len;\n\n@@ -85,9 +36,8 @@ static void t_addch(struct strbuf *buf, const void *data)\n \tcheck_char(buf->buf[buf->len], ==, '\\0');\n }\n\n-static void t_addstr(struct strbuf *buf, const void *data)\n+static void t_addstr(struct strbuf *buf, const char *text)\n {\n-\tconst char *text = data;\n \tsize_t len = strlen(text);\n \tsize_t orig_alloc = buf->alloc;\n \tsize_t orig_len = buf->len;\n@@ -105,18 +55,71 @@ static void t_addstr(struct strbuf *buf, const void *data)\n \tcheck_str(buf->buf + orig_len, text);\n }\n\n+static void t_release(struct strbuf *sb)\n+{\n+\tstrbuf_release(sb);\n+\tcheck_uint(sb->len, ==, 0);\n+\tcheck_uint(sb->alloc, ==, 0);\n+}\n+\n int cmd_main(int argc, const char **argv)\n {\n-\tif (!TEST(t_static_init(), \"static initialization works\"))\n-\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n-\tTEST(t_dynamic_init(), \"dynamic initialization works\");\n-\tTEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n-\tTEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n-\tTEST(setup_populated(t_addch, \"initial value\", \"a\"),\n-\t     \"strbuf_addch appends to initial value\");\n-\tTEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n-\tTEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n-\t     \"strbuf_addstr appends string to initial value\");\n+\tfor_test (\"static initialization works\") {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\n+\t\tif (!check_uint(buf.len, ==, 0) ||\n+\t\t    !check_uint(buf.alloc, ==, 0) ||\n+\t\t    !check_char(buf.buf[0], ==, '\\0'))\n+\t\t\ttest_skip_all(\"STRBUF_INIT is broken\");\n+\t}\n+\n+\tfor_test (\"dynamic initialization works\") {\n+\t\tstruct strbuf buf;\n+\n+\t\tstrbuf_init(&buf, 1024);\n+\t\tcheck(assert_sane_strbuf(&buf));\n+\t\tcheck_uint(buf.len, ==, 0);\n+\t\tcheck_uint(buf.alloc, >=, 1024);\n+\t\tcheck_char(buf.buf[0], ==, '\\0');\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tfor_test (\"strbuf_addch adds char\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tt_addch(&sb, 'a');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addch adds NUL char\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tt_addch(&sb, '\\0');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addch appends to initial value\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tt_addstr(&sb, \"initial value\");\n+\t\tt_addch(&sb, 'a');\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addstr adds string\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tt_addstr(&sb, \"hello there\");\n+\t\tt_release(&sb);\n+\t}\n+\n+\tfor_test (\"strbuf_addstr appends string to initial value\") {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tt_addstr(&sb, \"initial value\");\n+\t\tt_addstr(&sb, \"hello there\");\n+\t\tt_release(&sb);\n+\t}\n\n \treturn test_done();\n }\n--\n2.45.2\n"},{"id":"499293","messageId":"CAO_smVi2rJd5SDMsbbxzFUj28a_1KTgdHMz4DTKMsii+Wt5H_Q@mail.gmail.com","threadId":"61700","inReplyTo":"c51025cc-26e5-41e2-be56-94e141a64f5d@web.de","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-24T19:24:47Z","receivedAt":"2024-07-24T19:25:02Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Wed, Jul 24, 2024 at 7:53 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> The macro TEST only allows defining a test that consists of a single\n> expression.  Add a new macro, for_test, which provides a way to define\n> unit tests that are made up of one or more statements.\n\nPerhaps obvious to you and others, but I'm at a loss; can you\nelaborate on why this is a significant problem? Why is having the\ntests in a separate function a significant downside, and what are the\nbenefits of this new macro that counteract the \"magic\" here? I'm not\nseeing it.\n\nMacros like this require additional cognitive overhead to understand;\nI can't read `for_test (\"for_test passing test\") check_int(1, ==, 1);`\nand understand what's going on without going and reading the comment\nabove `for_test`. Unlike `TEST(<func>, \"<description>\")`, I can't even\n_guess_ what it's doing. This macro is project-specific, and this\nknowledge has to be attained by everyone wishing to even read this\ncode.\n\nI personally consider the patches that use this to be regressions in\nmy ability to read and understand the tests. As an example, one of the\ndiff hunks in the strbuf patch (patch #6 in the series) does this:\n-       TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n-            \"strbuf_addch appends to initial value\");\n+       for_test (\"strbuf_addch appends to initial value\") {\n+               struct strbuf sb = STRBUF_INIT;\n+               t_addstr(&sb, \"initial value\");\n+               t_addch(&sb, 'a');\n+               t_release(&sb);\n+       }\n\nBut the main simplification in that patch (not using\n`setup_populated`) can be achieved without `for_test`:\n\n+ static void t_addch_appends(void)\n+ {\n+     struct strbuf sb = STRBUF_INIT;\n+     t_addstr(&sb, \"initial value\");\n+     t_addch(&sb, 'a');\n+     t_release(&sb);\n+ }\n\n-       TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n-            \"strbuf_addch appends to initial value\");\n+       TEST(t_addch_appends(), \"strbuf_addch appends to initial value\");\n\nIt seems to me that all `for_test` is getting us there is an inlining\nof the test logic, which seems like it's optimizing for vertical space\nin the file. Maybe it's because I'm coming from a C++ environment at\n$JOB that's using Google's gunit and gmock frameworks, where every\ntest is in its own function and we usually don't even write the main\nfunction ourselves, but I have a preference for the separate\nfunctions.\n\nMaybe I'm in the minority, so only consider this at somewhere like a\n-0.5 on this series (fine with deferring to a reviewer who is\nsignificantly in favor of it, but if there aren't any, I'd lean\ntowards not landing this).\n"},{"id":"499324","messageId":"c7d280d5-6bc8-41c1-ae85-418e20b38a25@gmail.com","threadId":"61700","inReplyTo":"b4cbc8ce-5e39-491d-a6c9-627a7fd97c8f@gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-25T09:45:13Z","receivedAt":"2024-07-25T09:45:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 23/07/2024 14:24, Phillip Wood wrote:\n> On 22/07/2024 20:13, Kyle Lippincott wrote:\n>> On Sat, Jul 20, 2024 at 11:22 PM René Scharfe <l.s.r@web.de> wrote:\n>>>\n>> I think\n>> having multiple test functions is generally easier to understand, and\n>> the overhead isn't really relevant. It's not _as_ compact in the\n>> source file, and requires that we have both the TEST statement and the\n>> function (and forgetting the TEST statement means that we won't invoke\n>> the function). If that is the main issue we're facing here, I wonder\n>> if there's some other way of resolving that (such as unused function\n>> detection via some compiler flags; even if it's not detected on all\n>> platforms, getting at least one of the CI platforms should be\n>> sufficient to prevent the issue [but we should target as many as\n>> possible, so it's caught earlier than \"I tried to send it to the\n>> list\"])\n> \n> I also have a preference for function based tests but I do think \n> something like for_test() can be useful in certain situations.\n\nI'm no-longer convinced that this is really the case\n\n> For \n> example in test-ctype.c where testing the ctype macros leads to a lot of \n> repetition or a giant macro with a function based approach.\n\nHaving re-read the history of t/unit-tests/t-ctype.c I don't think the \nrepetition in the original version was really that bad. The objection to \nit seems to have been that one had to write the class name (for example \nisalpha()) twice - once when defining the test function and again when \ncalling it. I'm not sure that is really worth worrying about.\n\n> Another example is where we are using a helper function with several \n> inputs and we would prefer to write\n> \n> for_test(\"test 1\") {\n>      int input[] = ...\n>      int expect[] = ...\n> \n>      test_helper(input, expect);\n> \n> ...\n> \n> for_test(\"test 10\") {\n>      int input[] = ...\n>      int expect[] = ...\n> \n>      test_helper(input, expect);\n> }\n\nOne can use blocks to achieve the same outcome with the TEST() macro\n\n{\n       int input[] = ...\n       int expect[] = ...\n\n       TEST(test_helper(input, expect), \"test 1\");\n}\n\nSo overall I'm less convinced that adding something like for_test() is \nnecessary and I'm very convinced that calling it for_test() and using \n\"continue\" to exit a test early is going to confuse contributors.\n\nBest Wishes\n\nPhillip\n\n> rather then declaring all the input and expect variables up front with\n> \n>      int input_1 = ...\n>      int input_2 = ...\n>      ...\n>      int expect_1 = ...\n>      int expect_2 = ...\n> \n>      TEST(test_helper(input_1, expect_1), \"test 1\");\n>      ...\n>      TEST(test_helper(input_10, expect_10), \"test 10\");\n\n>> If others agree that this is a good simplification for the people\n>> reading the test code (and hopefully for the test author), I'm fine\n>> with this going in (with a different name). I'm not trying to veto the\n>> concept.\n> \n> That's my feeling too.\n> \n> Best Wishes\n> \n> Phillip\n> \n>>\n>>> +\n>>>   /*\n>>>    * Print a test plan, should be called before any tests. If the number\n>>>    * of tests is not known in advance test_done() will automatically\n>>> -- \n>>> 2.45.2\n>>>\n>>\n> \n"},{"id":"499325","messageId":"0a9fac5d-a8e7-4b4f-a681-878c1efdf488@gmail.com","threadId":"61700","inReplyTo":"CAO_smVi2rJd5SDMsbbxzFUj28a_1KTgdHMz4DTKMsii+Wt5H_Q@mail.gmail.com","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-07-25T09:45:29Z","receivedAt":"2024-07-25T09:45:31Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 24/07/2024 20:24, Kyle Lippincott wrote:\n> On Wed, Jul 24, 2024 at 7:53 AM René Scharfe <l.s.r@web.de> wrote:\n>>\n> Macros like this require additional cognitive overhead to understand;\n> I can't read `for_test (\"for_test passing test\") check_int(1, ==, 1);`\n> and understand what's going on without going and reading the comment\n> above `for_test`. Unlike `TEST(<func>, \"<description>\")`, I can't even\n> _guess_ what it's doing. This macro is project-specific, and this\n> knowledge has to be attained by everyone wishing to even read this\n> code.\n\nAs well as the obscure name and oddness of using \"continue\" to exit a \ntest early I think having two ways of defining tests also adds to the \ncognitive overhead of writing tests as one now has to decide which to use.\n\nAs you say below it is not clear to me that the changes in the last \nthree patches that start using for_test() in our existing tests are an \nimprovement.\n\nThis will be the last comment from me for a while as I'm about to go off \nthe list for a couple of weeks\n\nBest Wishes\n\nPhillip\n\n> I personally consider the patches that use this to be regressions in\n> my ability to read and understand the tests. As an example, one of the\n> diff hunks in the strbuf patch (patch #6 in the series) does this:\n> -       TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> -            \"strbuf_addch appends to initial value\");\n> +       for_test (\"strbuf_addch appends to initial value\") {\n> +               struct strbuf sb = STRBUF_INIT;\n> +               t_addstr(&sb, \"initial value\");\n> +               t_addch(&sb, 'a');\n> +               t_release(&sb);\n> +       }\n> \n> But the main simplification in that patch (not using\n> `setup_populated`) can be achieved without `for_test`:\n> \n> + static void t_addch_appends(void)\n> + {\n> +     struct strbuf sb = STRBUF_INIT;\n> +     t_addstr(&sb, \"initial value\");\n> +     t_addch(&sb, 'a');\n> +     t_release(&sb);\n> + }\n> \n> -       TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> -            \"strbuf_addch appends to initial value\");\n> +       TEST(t_addch_appends(), \"strbuf_addch appends to initial value\");\n> \n> It seems to me that all `for_test` is getting us there is an inlining\n> of the test logic, which seems like it's optimizing for vertical space\n> in the file. Maybe it's because I'm coming from a C++ environment at\n> $JOB that's using Google's gunit and gmock frameworks, where every\n> test is in its own function and we usually don't even write the main\n> function ourselves, but I have a preference for the separate\n> functions.\n> \n> Maybe I'm in the minority, so only consider this at somewhere like a\n> -0.5 on this series (fine with deferring to a reviewer who is\n> significantly in favor of it, but if there aren't any, I'd lean\n> towards not landing this).\n> \n"},{"id":"499353","messageId":"xmqqfrrxjw8f.fsf@gitster.g","threadId":"61700","inReplyTo":"CAO_smVi2rJd5SDMsbbxzFUj28a_1KTgdHMz4DTKMsii+Wt5H_Q@mail.gmail.com","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-25T16:02:24Z","receivedAt":"2024-07-25T16:02:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kyle Lippincott <spectral@google.com> writes:\n\n> But the main simplification in that patch (not using\n> `setup_populated`) can be achieved without `for_test`:\n>\n> + static void t_addch_appends(void)\n> + {\n> +     struct strbuf sb = STRBUF_INIT;\n> +     t_addstr(&sb, \"initial value\");\n> +     t_addch(&sb, 'a');\n> +     t_release(&sb);\n> + }\n>\n> -       TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n> -            \"strbuf_addch appends to initial value\");\n> +       TEST(t_addch_appends(), \"strbuf_addch appends to initial value\");\n\nYup.  I like that better when we are using TEST(), whether the other\nthing exists or not.\n\n> It seems to me that all `for_test` is getting us there is an inlining\n> of the test logic, which seems like it's optimizing for vertical space\n> in the file.\n\nI would consider that it optimizes for human readers who have to\nscan the file.  Having to jump around from caller to callee to\nunderstand the whole thing (rather, the description being away from\nthe callee, which is where the most of the logic is anyway) is one\ngripe I have with the approach taken by the TEST() thing.\n\n> Maybe it's because I'm coming from a C++ environment at\n> $JOB that's using Google's gunit and gmock frameworks, where every\n> test is in its own function and we usually don't even write the main\n> function ourselves, but I have a preference for the separate\n> functions.\n\nIf we do not have to write the main at all, then it would make the\nseparate function that implements a single test a lot more palatable,\nand we do not even have to implement and call TEST() macro ;-).\n\nThanks.\n\n"},{"id":"499379","messageId":"CAO_smVhq=MkQV3a6qJtDiFykvR4im7AX4hMfKMNcL5SegnOSLA@mail.gmail.com","threadId":"61700","inReplyTo":"xmqqfrrxjw8f.fsf@gitster.g","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-25T21:31:50Z","receivedAt":"2024-07-25T21:32:04Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Thu, Jul 25, 2024 at 9:02 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Kyle Lippincott <spectral@google.com> writes:\n>\n> > Maybe it's because I'm coming from a C++ environment at\n> > $JOB that's using Google's gunit and gmock frameworks, where every\n> > test is in its own function and we usually don't even write the main\n> > function ourselves, but I have a preference for the separate\n> > functions.\n>\n> If we do not have to write the main at all, then it would make the\n> separate function that implements a single test a lot more palatable,\n> and we do not even have to implement and call TEST() macro ;-).\n\nI feel like you're trying to bait me into coming up with a way of\navoiding the main function ;) But I don't think it can really be done\nin portable/vanilla C, unfortunately. I tried to think of a way to do\nthis, and they all involved some other system coming along and\nidentifying the tests and code-generating a main function, which also\nseems like too much magic to me.\n"},{"id":"499386","messageId":"xmqqv80szxgw.fsf@gitster.g","threadId":"61700","inReplyTo":"CAO_smVhq=MkQV3a6qJtDiFykvR4im7AX4hMfKMNcL5SegnOSLA@mail.gmail.com","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-26T02:41:19Z","receivedAt":"2024-07-26T02:41:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Kyle Lippincott <spectral@google.com> writes:\n\n>> > Maybe it's because I'm coming from a C++ environment at\n>> > $JOB that's using Google's gunit and gmock frameworks, where every\n>> > test is in its own function and we usually don't even write the main\n>> > function ourselves, but I have a preference for the separate\n>> > functions.\n>>\n>> If we do not have to write the main at all, then it would make the\n>> separate function that implements a single test a lot more palatable,\n>> and we do not even have to implement and call TEST() macro ;-).\n> ...\n> I tried to think of a way to do\n> this, and they all involved some other system coming along and\n> identifying the tests and code-generating a main function, which also\n> seems like too much magic to me.\n\nI thought that automatically generating the boilerplates from the\nvisible list of test functions and piecing them together with a\nsynthetic main was what was brought up as how libgit2 project does\nthis?  It does not sound all that involved and I do not find it a\nrocket science.\n\nIn any case, a well written framework soon becomes \"magic\" anyway.\nOur scripted test suite, back when Pasky and I invented it, was\nunknown to many people, would have looked very far away from any\nday-to-day shell scripts, and are full of magic.  Modern versions\ngained even more magic than the what we had in the olden days.  But\nI hear these days other projects imitate the pattern, so it may no\nlonger so much \"magic\" to many audiences.\n\nIf we use a well-written framework for C unit tests, I suspect that\nthe same thing would happen eventually.  An important point is the\n\"well-written\" part, which needs to hide the ugly implementation\ndetails well without revealing it in corners.  As Phillip said, a\nloop construct similar to for() that you are not allowed to break or\ncontinue may not qualify exactly for that reason.\n\n\n\n"},{"id":"499440","messageId":"ZqOc9vxdD4qttkFs@tanuki","threadId":"61700","inReplyTo":"xmqqv80szxgw.fsf@gitster.g","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-26T12:56:22Z","receivedAt":"2024-07-26T12:56:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jul 25, 2024 at 07:41:19PM -0700, Junio C Hamano wrote:\n> Kyle Lippincott <spectral@google.com> writes:\n> \n> >> > Maybe it's because I'm coming from a C++ environment at\n> >> > $JOB that's using Google's gunit and gmock frameworks, where every\n> >> > test is in its own function and we usually don't even write the main\n> >> > function ourselves, but I have a preference for the separate\n> >> > functions.\n> >>\n> >> If we do not have to write the main at all, then it would make the\n> >> separate function that implements a single test a lot more palatable,\n> >> and we do not even have to implement and call TEST() macro ;-).\n> > ...\n> > I tried to think of a way to do\n> > this, and they all involved some other system coming along and\n> > identifying the tests and code-generating a main function, which also\n> > seems like too much magic to me.\n> \n> I thought that automatically generating the boilerplates from the\n> visible list of test functions and piecing them together with a\n> synthetic main was what was brought up as how libgit2 project does\n> this?  It does not sound all that involved and I do not find it a\n> rocket science.\n\nAs I said when mentioning how libgit2 does it, I'd be happy to present a\nworking prototype for this if others think it was potentially useful.\nUntil now I didn't hear any positive feedback thoguh, you're the first\none saying that this might be something that is worth doing.\n\nI just want to avoid wasting time on something that has no chance of\nlanding in the first place.\n\nPatrick\n"},{"id":"499454","messageId":"xmqqed7gxhyz.fsf@gitster.g","threadId":"61700","inReplyTo":"ZqOc9vxdD4qttkFs@tanuki","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-26T15:59:00Z","receivedAt":"2024-07-26T15:59:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> As I said when mentioning how libgit2 does it, I'd be happy to present a\n> working prototype for this if others think it was potentially useful.\n> Until now I didn't hear any positive feedback thoguh, you're the first\n> one saying that this might be something that is worth doing.\n>\n> I just want to avoid wasting time on something that has no chance of\n> landing in the first place.\n\nIt takes such a sizeable effort to make it pleasant to use the\nTEST() thing, it becomes somewhat questionable that it is worth the\neffort.  What I was hoping to see was hinted in the part you did not\nquote ;-)\n\nIf there is an existing well-written framework for C unit tests we\ncan apply to our project, it may not have to be so much \"magic\".  If\nyou think theirs is not so good and worth copying, then let's not.\n\nThanks.\n\n"},{"id":"499506","messageId":"ZqdldZE2MV-Pkuu-@tanuki","threadId":"61700","inReplyTo":"xmqqed7gxhyz.fsf@gitster.g","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-29T09:48:37Z","receivedAt":"2024-07-29T09:48:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jul 26, 2024 at 08:59:00AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> If there is an existing well-written framework for C unit tests we\n> can apply to our project, it may not have to be so much \"magic\".  If\n> you think theirs is not so good and worth copying, then let's not.\n\nWith \"theirs\" being the libgit2 one? I always enjoyed using it, and it\nis standalone nowadays and called \"clar\" [1]. The biggest downside of it\nis that it depends on Python to auto-generate the test \"main\" function,\nwhich is not really a good fit for the Git project. So ultimately, we'd\nhave to adapt the heart of clar anyway. It's not all that involved, but\nsomething to keep in mind if we wanted to use it.\n\nPatrick\n\n[1]: https://github.com/clar-test/clar\n"},{"id":"499528","messageId":"xmqq8qxkhvt3.fsf@gitster.g","threadId":"61700","inReplyTo":"ZqdldZE2MV-Pkuu-@tanuki","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-07-29T18:55:52Z","receivedAt":"2024-07-29T18:56:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> is standalone nowadays and called \"clar\" [1]. The biggest downside of it\n> is that it depends on Python to auto-generate the test \"main\" function,\n> which is not really a good fit for the Git project.\n\nIs that because Python is optional (like, we only use it for\noptional Perforce thing and import-zip in contrib), or are there\nother concerns?\n\nUnlike these components, unit tests are not even for the end-user\nconsumers, so if it is Python that you find it a blocker, I do not\nsee a reason to reject it.  The thing looked like a simple script\nthat does not use any tricky language construct or modules.\n"},{"id":"499551","messageId":"Zqhwwudf-yjvJ_By@tanuki","threadId":"61700","inReplyTo":"xmqq8qxkhvt3.fsf@gitster.g","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-30T04:49:06Z","receivedAt":"2024-07-30T04:49:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Jul 29, 2024 at 11:55:52AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > is standalone nowadays and called \"clar\" [1]. The biggest downside of it\n> > is that it depends on Python to auto-generate the test \"main\" function,\n> > which is not really a good fit for the Git project.\n> \n> Is that because Python is optional (like, we only use it for\n> optional Perforce thing and import-zip in contrib), or are there\n> other concerns?\n> \n> Unlike these components, unit tests are not even for the end-user\n> consumers, so if it is Python that you find it a blocker, I do not\n> see a reason to reject it.  The thing looked like a simple script\n> that does not use any tricky language construct or modules.\n\nNo concerns other than adding another mandatory dependency for devs. We\nalready depend on Perl and Shell, so adding Python to the stack feels\nsuboptimal to me.\n\nAs you say though, the script isn't all that involved. So it would also\nbe possible to port it to Perl if we want to do that, I guess.\n\nPatrick\n"},{"id":"499599","messageId":"ce00cc56-4671-4ef6-9a43-db13acec3e46@web.de","threadId":"61700","inReplyTo":"c7d280d5-6bc8-41c1-ae85-418e20b38a25@gmail.com","subject":"Re: [PATCH v2 2/6] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:00:00Z","receivedAt":"2024-07-30T14:00:17Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 25.07.24 um 11:45 schrieb Phillip Wood:\n> On 23/07/2024 14:24, Phillip Wood wrote:\n>\n>> For example in test-ctype.c where testing the ctype macros leads to\n>> a lot of repetition or a giant macro with a function based\n>> approach.\n>\n> Having re-read the history of t/unit-tests/t-ctype.c I don't think\n> the repetition in the original version was really that bad. The\n> objection to it seems to have been that one had to write the class\n> name (for example isalpha()) twice - once when defining the test\n> function and again when calling it. I'm not sure that is really worth\n> worrying about.\n\nI can't see the purpose of requiring repetition like that.  It's an\nunnecessary obstacle, small as it may be.  In the case of t-ctype it was\na regression to the original test helper code.  It's easy to avoid with\nexisting functions, except that those are off-limits to allow for future\nchanges.  So it's just a matter of packaging them nicely.\n\n> So overall I'm less convinced that adding something like for_test()\n> is necessary and I'm very convinced that calling it for_test() and\n> using \"continue\" to exit a test early is going to confuse\n> contributors.\n\nI didn't expect anybody would want to use continue or break in a test,\nbut that's probably naive.  I got carried away there by the prospect\nof a trivial macro-only solution, but alas, it's too clunky.  Two steps\nback..\n\nRené\n"},{"id":"499600","messageId":"53c1eaeb-6106-492c-9c44-13a7cd1bd0d0@web.de","threadId":"61700","inReplyTo":"Zqhwwudf-yjvJ_By@tanuki","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:00:07Z","receivedAt":"2024-07-30T14:00:27Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 30.07.24 um 06:49 schrieb Patrick Steinhardt:\n> On Mon, Jul 29, 2024 at 11:55:52AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>>> is standalone nowadays and called \"clar\" [1]. The biggest downside of it\n>>> is that it depends on Python to auto-generate the test \"main\" function,\n>>> which is not really a good fit for the Git project.\n>>\n>> Is that because Python is optional (like, we only use it for\n>> optional Perforce thing and import-zip in contrib), or are there\n>> other concerns?\n>>\n>> Unlike these components, unit tests are not even for the end-user\n>> consumers, so if it is Python that you find it a blocker, I do not\n>> see a reason to reject it.  The thing looked like a simple script\n>> that does not use any tricky language construct or modules.\n>\n> No concerns other than adding another mandatory dependency for devs. We\n> already depend on Perl and Shell, so adding Python to the stack feels\n> suboptimal to me.\n>\n> As you say though, the script isn't all that involved. So it would also\n> be possible to port it to Perl if we want to do that, I guess.\n\nFrom the point of view of a \"minimal C unit testing framework\" surely\nthe implementation language with the best dependency story would be C.\nPerhaps it could then also test itself.  On the other hand, writing a\nstring handling program in 2024 in C is probably not the smartest idea.\n\nThen again, generate.py uses a non-greedy regex to remove single-line\ncomments, which seems wrong, and doesn't seem to support preprocessor\ndirectives like #if and #ifdef, so whole tests can't be disabled this\nway.  And it uses pickle to cache results; does that mean it would be\nslow without it?  Anyway, point being that parsing C isn't so easy\nusing Python either.\n\nRené\n"},{"id":"499602","messageId":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","threadId":"61700","inReplyTo":"85b6b8a9-ee5f-42ab-bcbc-49976b30ef33@web.de","subject":"[PATCH v4 0/6] add and use if_test to simplify tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:03:36Z","receivedAt":"2024-07-30T14:03:45Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Changes since v3:\n- Replace for_test with if_test, a more robust conditional-like macro.\n  It stores description and location like v1, but without using strbuf.\n- Drop patch 7 for t-strbuf to avoid hindering Kyle's cleanup idea.\n\nSide note: Created with --creation-factor=60, as the default of 999 in\nv2.46.0 pairs dropped patch 7 with new patch 4 and reports old patch 4\nas dropped, which is confusing.\n\n  t0080: use here-doc test body\n  unit-tests: show location of checks outside of tests\n  unit-tests: add if_test\n  t-ctype: use if_test\n  t-reftable-basics: use if_test\n  t-strvec: use if_test\n\n .clang-format                    |   5 +\n t/helper/test-example-tap.c      |  35 +++\n t/t0080-unit-test-output.sh      |  60 ++++--\n t/unit-tests/t-ctype.c           |   4 +-\n t/unit-tests/t-reftable-basics.c | 228 +++++++++-----------\n t/unit-tests/t-strvec.c          | 356 ++++++++++++++-----------------\n t/unit-tests/test-lib.c          |  36 +++-\n t/unit-tests/test-lib.h          |  20 ++\n 8 files changed, 405 insertions(+), 339 deletions(-)\n\nRange-Diff gegen v3:\n1:  497002df9e = 1:  497002df9e t0080: use here-doc test body\n2:  0c1503fb5a = 2:  f85d4f9455 unit-tests: show location of checks outside of tests\n3:  27f1f18b3d < -:  ---------- unit-tests: add for_test\n-:  ---------- > 3:  77c7dfa1ad unit-tests: add if_test\n4:  98a1e7abdf ! 4:  63fba50876 t-ctype: use for_test\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-ctype: use for_test\n    +    t-ctype: use if_test\n\n    -    Use the documented macro for_test instead of the internal functions\n    +    Use the documented macro if_test instead of the internal functions\n         test__run_begin() and test__run_end(), which are supposed to be private\n         to the unit test framework.\n\n    @@ t/unit-tests/t-ctype.c\n      \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n     -\tint skip = test__run_begin(); \\\n     -\tif (!skip) { \\\n    -+\tfor_test (#class \" works\") { \\\n    ++\tif_test (#class \" works\") { \\\n      \t\tfor (int i = 0; i < 256; i++) { \\\n      \t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n      \t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n5:  7c954f0864 ! 5:  ab86673484 t-reftable-basics: use for_test\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-reftable-basics: use for_test\n    +    t-reftable-basics: use if_test\n\n         The macro TEST takes a single expression.  If a test requires multiple\n         statements then they need to be placed in a function that's called in\n         the TEST expression.\n\n         Remove the overhead of defining and calling single-use functions by\n    -    using for_test instead.\n    +    using if_test instead.\n\n         Run the tests in the order of definition.  We can reorder them like that\n         because they are independent.  Technically this changes the output, but\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\t\tstruct integer_needle_lesseq_args args = {\n     -\t\t\t.haystack = haystack,\n     -\t\t\t.needle = testcases[i].needle,\n    -+\tfor_test (\"binary search with binsearch works\") {\n    ++\tif_test (\"binary search with binsearch works\") {\n     +\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n     +\t\tstruct {\n     +\t\t\tint needle;\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n     -\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n     -\tconst char *c[] = { \"a\", \"b\", NULL };\n    -+\tfor_test (\"names_length retuns size of a NULL-terminated string array\") {\n    ++\tif_test (\"names_length retuns size of a NULL-terminated string array\") {\n     +\t\tconst char *a[] = { \"a\", \"b\", NULL };\n     +\t\tcheck_int(names_length(a), ==, 2);\n     +\t}\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tcheck(!names_equal(a, b));\n     -\tcheck(!names_equal(a, c));\n     -}\n    -+\tfor_test (\"names_equal compares NULL-terminated string arrays\") {\n    ++\tif_test (\"names_equal compares NULL-terminated string arrays\") {\n     +\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n     +\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n     +\t\tconst char *c[] = { \"a\", \"b\", NULL };\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tcheck(!out[2]);\n     -\tfree_names(out);\n     -}\n    -+\tfor_test (\"parse_names works for basic input\") {\n    ++\tif_test (\"parse_names works for basic input\") {\n     +\t\tchar in1[] = \"line\\n\";\n     +\t\tchar in2[] = \"a\\nb\\nc\";\n     +\t\tchar **out = NULL;\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n     -\t\tstrbuf_reset(&a);\n     -\t\tstrbuf_reset(&b);\n    -+\tfor_test (\"parse_names drops empty string\") {\n    ++\tif_test (\"parse_names drops empty string\") {\n     +\t\tchar in[] = \"a\\n\\nb\\n\";\n     +\t\tchar **out = NULL;\n     +\t\tparse_names(in, strlen(in), &out);\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tout = get_be24(dest);\n     -\tcheck_int(in, ==, out);\n     -}\n    -+\tfor_test (\"common_prefix_size works\") {\n    ++\tif_test (\"common_prefix_size works\") {\n     +\t\tstruct strbuf a = STRBUF_INIT;\n     +\t\tstruct strbuf b = STRBUF_INIT;\n     +\t\tstruct {\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     -\tTEST(test_names_equal(), \"names_equal compares NULL-terminated string arrays\");\n     -\tTEST(test_u24_roundtrip(), \"put_be24 and get_be24 work\");\n     -\tTEST(test_u16_roundtrip(), \"put_be16 and get_be16 work\");\n    -+\tfor_test (\"put_be24 and get_be24 work\") {\n    ++\tif_test (\"put_be24 and get_be24 work\") {\n     +\t\tuint32_t in = 0x112233;\n     +\t\tuint8_t dest[3];\n     +\t\tuint32_t out;\n    @@ t/unit-tests/t-reftable-basics.c: static int integer_needle_lesseq(size_t i, voi\n     +\t\tcheck_int(in, ==, out);\n     +\t}\n     +\n    -+\tfor_test (\"put_be16 and get_be16 work\") {\n    ++\tif_test (\"put_be16 and get_be16 work\") {\n     +\t\tuint32_t in = 0xfef1;\n     +\t\tuint8_t dest[3];\n     +\t\tuint32_t out;\n6:  d619a756d7 ! 6:  64bb731ba6 t-strvec: use for_test\n    @@ Metadata\n     Author: René Scharfe <l.s.r@web.de>\n\n      ## Commit message ##\n    -    t-strvec: use for_test\n    +    t-strvec: use if_test\n\n         The macro TEST takes a single expression.  If a test requires multiple\n         statements then they need to be placed in a function that's called in\n         the TEST expression.\n\n         Remove the cognitive overhead of defining and calling single-use\n    -    functions by using for_test instead.\n    +    functions by using if_test instead.\n    +\n    +    Signed-off-by: René Scharfe <l.s.r@web.de>\n\n      ## t/unit-tests/t-strvec.c ##\n     @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_uint(vec.nr, ==, 0);\n     -\tcheck_uint(vec.alloc, ==, 0);\n     -}\n    -+\tfor_test (\"static initialization\") {\n    ++\tif_test (\"static initialization\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n     +\t\tcheck_uint(vec.nr, ==, 0);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_uint(vec.nr, ==, 0);\n     -\tcheck_uint(vec.alloc, ==, 0);\n     -}\n    -+\tfor_test (\"dynamic initialization\") {\n    ++\tif_test (\"dynamic initialization\") {\n     +\t\tstruct strvec vec;\n     +\t\tstrvec_init(&vec);\n     +\t\tcheck_pointer_eq(vec.v, empty_strvec);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_uint(vec.nr, ==, 0);\n     -\tcheck_uint(vec.alloc, ==, 0);\n     -}\n    -+\tfor_test (\"clear\") {\n    ++\tif_test (\"clear\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_push(&vec, \"foo\");\n     +\t\tstrvec_clear(&vec);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -static void t_push(void)\n     -{\n     -\tstruct strvec vec = STRVEC_INIT;\n    -+\tfor_test (\"push\") {\n    ++\tif_test (\"push\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n\n     -\tstrvec_push(&vec, \"foo\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo: 1\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"pushf\") {\n    ++\tif_test (\"pushf\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n     +\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"pushl\") {\n    ++\tif_test (\"pushl\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\t\t\"foo\", \"bar\", \"baz\", NULL,\n     -\t};\n     -\tstruct strvec vec = STRVEC_INIT;\n    -+\tfor_test (\"pushv\") {\n    ++\tif_test (\"pushv\") {\n     +\t\tconst char *strings[] = {\n     +\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n     +\t\t};\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"replace at head\") {\n    ++\tif_test (\"replace at head\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_replace(&vec, 0, \"replaced\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"replace at tail\") {\n    ++\tif_test (\"replace at tail\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_replace(&vec, 2, \"replaced\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"replace in between\") {\n    ++\tif_test (\"replace in between\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_replace(&vec, 1, \"replaced\");\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"oo\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"replace with substring\") {\n    ++\tif_test (\"replace with substring\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", NULL);\n     +\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"remove at head\") {\n    ++\tif_test (\"remove at head\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_remove(&vec, 0);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"remove at tail\") {\n    ++\tif_test (\"remove at tail\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_remove(&vec, 2);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"remove in between\") {\n    ++\tif_test (\"remove in between\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_remove(&vec, 1);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"pop with empty array\") {\n    ++\tif_test (\"pop with empty array\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pop(&vec);\n     +\t\tcheck_strvec(&vec, NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"pop with non-empty array\") {\n    ++\tif_test (\"pop with non-empty array\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     +\t\tstrvec_pop(&vec);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"split empty string\") {\n    ++\tif_test (\"split empty string\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"\");\n     +\t\tcheck_strvec(&vec, NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"split single item\") {\n    ++\tif_test (\"split single item\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"foo\");\n     +\t\tcheck_strvec(&vec, \"foo\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"split multiple items\") {\n    ++\tif_test (\"split multiple items\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"foo bar baz\");\n     +\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"split whitespace only\") {\n    ++\tif_test (\"split whitespace only\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \" \\t\\n\");\n     +\t\tcheck_strvec(&vec, NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n     -\tstrvec_clear(&vec);\n     -}\n    -+\tfor_test (\"split multiple consecutive whitespaces\") {\n    ++\tif_test (\"split multiple consecutive whitespaces\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n     +\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n    @@ t/unit-tests/t-strvec.c: static void check_strvec_loc(const char *loc, struct st\n     -{\n     -\tstruct strvec vec = STRVEC_INIT;\n     -\tconst char **detached;\n    -+\tfor_test (\"detach\") {\n    ++\tif_test (\"detach\") {\n     +\t\tstruct strvec vec = STRVEC_INIT;\n     +\t\tconst char **detached;\n\n7:  ea088728ad < -:  ---------- t-strbuf: use for_test\n--\n2.46.0\n"},{"id":"499603","messageId":"23498c37-1e30-45a9-92ae-d31a05778fca@web.de","threadId":"61700","inReplyTo":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","subject":"[PATCH v4 1/6] t0080: use here-doc test body","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:05:58Z","receivedAt":"2024-07-30T14:06:06Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Improve the readability of the expected output by using a here-doc for\nthe test body and replacing the unwieldy ${SQ} references with literal\nsingle quotes.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/t0080-unit-test-output.sh | 20 ++++++++++----------\n 1 file changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex 7bbb065d58..9ec47b7360 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -5,7 +5,7 @@ test_description='Test the output of the unit test framework'\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n\n-test_expect_success 'TAP output from unit tests' '\n+test_expect_success 'TAP output from unit tests' - <<\\EOT\n \tcat >expect <<-EOF &&\n \tok 1 - passing test\n \tok 2 - passing test and assertion return 1\n@@ -16,12 +16,12 @@ test_expect_success 'TAP output from unit tests' '\n \tok 4 - failing test and assertion return 0\n \tnot ok 5 - passing TEST_TODO() # TODO\n \tok 6 - passing TEST_TODO() returns 1\n-\t# todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26\n+\t# todo check 'check(x)' succeeded at t/helper/test-example-tap.c:26\n \tnot ok 7 - failing TEST_TODO()\n \tok 8 - failing TEST_TODO() returns 0\n \t# check \"0\" failed at t/helper/test-example-tap.c:31\n \t# skipping test - missing prerequisite\n-\t# skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33\n+\t# skipping check '1' at t/helper/test-example-tap.c:33\n \tok 9 - test_skip() # SKIP\n \tok 10 - skipped test returns 1\n \t# skipping test - missing prerequisite\n@@ -39,12 +39,12 @@ test_expect_success 'TAP output from unit tests' '\n \t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:63\n \t#    left: \"NULL\"\n \t#   right: NULL\n-\t# check \"${SQ}a${SQ} == ${SQ}\\n${SQ}\" failed at t/helper/test-example-tap.c:64\n-\t#    left: ${SQ}a${SQ}\n-\t#   right: ${SQ}\\012${SQ}\n-\t# check \"${SQ}\\\\\\\\${SQ} == ${SQ}\\\\${SQ}${SQ}\" failed at t/helper/test-example-tap.c:65\n-\t#    left: ${SQ}\\\\\\\\${SQ}\n-\t#   right: ${SQ}\\\\${SQ}${SQ}\n+\t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:64\n+\t#    left: 'a'\n+\t#   right: '\\012'\n+\t# check \"'\\\\\\\\' == '\\\\''\" failed at t/helper/test-example-tap.c:65\n+\t#    left: '\\\\\\\\'\n+\t#   right: '\\\\''\n \tnot ok 17 - messages from failing string and char comparison\n \t# BUG: test has no checks at t/helper/test-example-tap.c:92\n \tnot ok 18 - test with no checks\n@@ -54,6 +54,6 @@ test_expect_success 'TAP output from unit tests' '\n\n \t! test-tool example-tap >actual &&\n \ttest_cmp expect actual\n-'\n+EOT\n\n test_done\n--\n2.46.0\n"},{"id":"499604","messageId":"4b27cee7-98eb-41ac-a68b-44f42e15a5d2@web.de","threadId":"61700","inReplyTo":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","subject":"[PATCH v4 2/6] unit-tests: show location of checks outside of tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:07:00Z","receivedAt":"2024-07-30T14:07:08Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Checks outside of tests are caught at runtime and reported like this:\n\n Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n\nThe assert() call aborts the unit test and doesn't reveal the location\nor even the type of the offending check, as test_assert() is called by\nall of them.\n\nHandle it like the opposite case, a test without any checks: Don't\nabort, but report the location of the actual check, along with a message\nexplaining the situation.  The output for example above becomes:\n\n # BUG: check outside of test at t/helper/test-example-tap.c:75\n\n... and the unit test program continues and indicates the error in its\nexit code at the end.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/helper/test-example-tap.c | 2 ++\n t/t0080-unit-test-output.sh | 5 +++--\n t/unit-tests/test-lib.c     | 7 ++++++-\n 3 files changed, 11 insertions(+), 3 deletions(-)\n\ndiff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\nindex d072ad559f..79c12b01cd 100644\n--- a/t/helper/test-example-tap.c\n+++ b/t/helper/test-example-tap.c\n@@ -72,6 +72,8 @@ static void t_empty(void)\n\n int cmd__example_tap(int argc, const char **argv)\n {\n+\tcheck(1);\n+\n \ttest_res = TEST(check_res = check_int(1, ==, 1), \"passing test\");\n \tTEST(t_res(1), \"passing test and assertion return 1\");\n \ttest_res = TEST(check_res = check_int(1, ==, 2), \"failing test\");\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex 9ec47b7360..fe221f3bdb 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -7,9 +7,10 @@ TEST_PASSES_SANITIZE_LEAK=true\n\n test_expect_success 'TAP output from unit tests' - <<\\EOT\n \tcat >expect <<-EOF &&\n+\t# BUG: check outside of test at t/helper/test-example-tap.c:75\n \tok 1 - passing test\n \tok 2 - passing test and assertion return 1\n-\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n+\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:79\n \t#    left: 1\n \t#   right: 2\n \tnot ok 3 - failing test\n@@ -46,7 +47,7 @@ test_expect_success 'TAP output from unit tests' - <<\\EOT\n \t#    left: '\\\\\\\\'\n \t#   right: '\\\\''\n \tnot ok 17 - messages from failing string and char comparison\n-\t# BUG: test has no checks at t/helper/test-example-tap.c:92\n+\t# BUG: test has no checks at t/helper/test-example-tap.c:94\n \tnot ok 18 - test with no checks\n \tok 19 - test with no checks returns 0\n \t1..19\ndiff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\nindex 3c513ce59a..989dc758e6 100644\n--- a/t/unit-tests/test-lib.c\n+++ b/t/unit-tests/test-lib.c\n@@ -264,7 +264,12 @@ static void test_todo(void)\n\n int test_assert(const char *location, const char *check, int ok)\n {\n-\tassert(ctx.running);\n+\tif (!ctx.running) {\n+\t\ttest_msg(\"BUG: check outside of test at %s\",\n+\t\t\t make_relative(location));\n+\t\tctx.failed = 1;\n+\t\treturn 0;\n+\t}\n\n \tif (ctx.result == RESULT_SKIP) {\n \t\ttest_msg(\"skipping check '%s' at %s\", check,\n--\n2.46.0\n"},{"id":"499605","messageId":"da902de9-288e-4783-8b4b-a2a968d1e1f8@web.de","threadId":"61700","inReplyTo":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","subject":"[PATCH v4 3/6] unit-tests: add if_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:08:19Z","receivedAt":"2024-07-30T14:08:28Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST only allows defining a test that consists of a single\nexpression.  Add a new macro, if_test, which provides a way to define\nunit tests that are made up of one or more statements.\n\nif_test allows defining self-contained tests en bloc, a bit like\ntest_expect_success does for regular tests.  It acts like a conditional;\nthe test body is executed if test_skip_all() had not been called before.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n .clang-format               |  5 +++++\n t/helper/test-example-tap.c | 33 +++++++++++++++++++++++++++++++++\n t/t0080-unit-test-output.sh | 35 ++++++++++++++++++++++++++++++++++-\n t/unit-tests/test-lib.c     | 29 +++++++++++++++++++++++++++++\n t/unit-tests/test-lib.h     | 20 ++++++++++++++++++++\n 5 files changed, 121 insertions(+), 1 deletion(-)\n\ndiff --git a/.clang-format b/.clang-format\nindex 6408251577..4c6d317508 100644\n--- a/.clang-format\n+++ b/.clang-format\n@@ -169,6 +169,11 @@ ForEachMacros:\n   - 'strmap_for_each_entry'\n   - 'strset_for_each_entry'\n\n+# A list of macros that should be interpreted as conditionals instead of as\n+# function calls.\n+IfMacros:\n+  - 'if_test'\n+\n # The maximum number of consecutive empty lines to keep.\n MaxEmptyLinesToKeep: 1\n\ndiff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\nindex 79c12b01cd..914af88e0a 100644\n--- a/t/helper/test-example-tap.c\n+++ b/t/helper/test-example-tap.c\n@@ -94,5 +94,38 @@ int cmd__example_tap(int argc, const char **argv)\n \ttest_res = TEST(t_empty(), \"test with no checks\");\n \tTEST(check_int(test_res, ==, 0), \"test with no checks returns 0\");\n\n+\tif_test (\"if_test passing test\")\n+\t\tcheck_int(1, ==, 1);\n+\tif_test (\"if_test failing test\")\n+\t\tcheck_int(1, ==, 2);\n+\tif_test (\"if_test passing TEST_TODO()\")\n+\t\tTEST_TODO(check(0));\n+\tif_test (\"if_test failing TEST_TODO()\")\n+\t\tTEST_TODO(check(1));\n+\tif_test (\"if_test test_skip()\") {\n+\t\tcheck(0);\n+\t\ttest_skip(\"missing prerequisite\");\n+\t\tcheck(1);\n+\t}\n+\tif_test (\"if_test test_skip() inside TEST_TODO()\")\n+\t\tTEST_TODO((test_skip(\"missing prerequisite\"), 1));\n+\tif_test (\"if_test TEST_TODO() after failing check\") {\n+\t\tcheck(0);\n+\t\tTEST_TODO(check(0));\n+\t}\n+\tif_test (\"if_test failing check after TEST_TODO()\") {\n+\t\tcheck(1);\n+\t\tTEST_TODO(check(0));\n+\t\tcheck(0);\n+\t}\n+\tif_test (\"if_test messages from failing string and char comparison\") {\n+\t\tcheck_str(\"\\thello\\\\\", \"there\\\"\\n\");\n+\t\tcheck_str(\"NULL\", NULL);\n+\t\tcheck_char('a', ==, '\\n');\n+\t\tcheck_char('\\\\', ==, '\\'');\n+\t}\n+\tif_test (\"if_test test with no checks\")\n+\t\t; /* nothing */\n+\n \treturn test_done();\n }\ndiff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\nindex fe221f3bdb..3c369c88e2 100755\n--- a/t/t0080-unit-test-output.sh\n+++ b/t/t0080-unit-test-output.sh\n@@ -50,7 +50,40 @@ test_expect_success 'TAP output from unit tests' - <<\\EOT\n \t# BUG: test has no checks at t/helper/test-example-tap.c:94\n \tnot ok 18 - test with no checks\n \tok 19 - test with no checks returns 0\n-\t1..19\n+\tok 20 - if_test passing test\n+\t# check \"1 == 2\" failed at t/helper/test-example-tap.c:100\n+\t#    left: 1\n+\t#   right: 2\n+\tnot ok 21 - if_test failing test\n+\tnot ok 22 - if_test passing TEST_TODO() # TODO\n+\t# todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104\n+\tnot ok 23 - if_test failing TEST_TODO()\n+\t# check \"0\" failed at t/helper/test-example-tap.c:106\n+\t# skipping test - missing prerequisite\n+\t# skipping check '1' at t/helper/test-example-tap.c:108\n+\tok 24 - if_test test_skip() # SKIP\n+\t# skipping test - missing prerequisite\n+\tok 25 - if_test test_skip() inside TEST_TODO() # SKIP\n+\t# check \"0\" failed at t/helper/test-example-tap.c:113\n+\tnot ok 26 - if_test TEST_TODO() after failing check\n+\t# check \"0\" failed at t/helper/test-example-tap.c:119\n+\tnot ok 27 - if_test failing check after TEST_TODO()\n+\t# check \"!strcmp(\"\\thello\\\\\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:122\n+\t#    left: \"\\011hello\\\\\\\\\"\n+\t#   right: \"there\\\"\\012\"\n+\t# check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:123\n+\t#    left: \"NULL\"\n+\t#   right: NULL\n+\t# check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:124\n+\t#    left: 'a'\n+\t#   right: '\\012'\n+\t# check \"'\\\\\\\\' == '\\\\''\" failed at t/helper/test-example-tap.c:125\n+\t#    left: '\\\\\\\\'\n+\t#   right: '\\\\''\n+\tnot ok 28 - if_test messages from failing string and char comparison\n+\t# BUG: test has no checks at t/helper/test-example-tap.c:127\n+\tnot ok 29 - if_test test with no checks\n+\t1..29\n \tEOF\n\n \t! test-tool example-tap >actual &&\ndiff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\nindex 989dc758e6..fa1f95965c 100644\n--- a/t/unit-tests/test-lib.c\n+++ b/t/unit-tests/test-lib.c\n@@ -16,6 +16,8 @@ static struct {\n \tunsigned running :1;\n \tunsigned skip_all :1;\n \tunsigned todo :1;\n+\tchar location[100];\n+\tchar description[100];\n } ctx = {\n \t.lazy_plan = 1,\n \t.result = RESULT_NONE,\n@@ -125,6 +127,8 @@ void test_plan(int count)\n\n int test_done(void)\n {\n+\tif (ctx.running && ctx.location[0] && ctx.description[0])\n+\t\ttest__run_end(1, ctx.location, \"%s\", ctx.description);\n \tassert(!ctx.running);\n\n \tif (ctx.lazy_plan)\n@@ -167,13 +171,38 @@ void test_skip_all(const char *format, ...)\n \tva_end(ap);\n }\n\n+void test__run_describe(const char *location, const char *format, ...)\n+{\n+\tva_list ap;\n+\tint len;\n+\n+\tassert(ctx.running);\n+\tassert(!ctx.location[0]);\n+\tassert(!ctx.description[0]);\n+\n+\txsnprintf(ctx.location, sizeof(ctx.location), \"%s\",\n+\t\t  make_relative(location));\n+\n+\tva_start(ap, format);\n+\tlen = vsnprintf(ctx.description, sizeof(ctx.description), format, ap);\n+\tva_end(ap);\n+\tif (len < 0)\n+\t\tdie(\"unable to format message: %s\", format);\n+\tif (len >= sizeof(ctx.description))\n+\t\tBUG(\"ctx.description too small to format %s\", format);\n+}\n+\n int test__run_begin(void)\n {\n+\tif (ctx.running && ctx.location[0] && ctx.description[0])\n+\t\ttest__run_end(1, ctx.location, \"%s\", ctx.description);\n \tassert(!ctx.running);\n\n \tctx.count++;\n \tctx.result = RESULT_NONE;\n \tctx.running = 1;\n+\tctx.location[0] = '\\0';\n+\tctx.description[0] = '\\0';\n\n \treturn ctx.skip_all;\n }\ndiff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h\nindex 2de6d715d5..f15dceb29e 100644\n--- a/t/unit-tests/test-lib.h\n+++ b/t/unit-tests/test-lib.h\n@@ -14,6 +14,23 @@\n \ttest__run_end(test__run_begin() ? 0 : (t, 1),\t\\\n \t\t      TEST_LOCATION(),  __VA_ARGS__)\n\n+/*\n+ * Run a test unless test_skip_all() has been called.  Acts like a\n+ * conditional; the test body is expected as a statement or block after\n+ * the closing parenthesis.  The description for each test should be\n+ * unique.  E.g.:\n+ *\n+ *  if_test (\"something else %d %d\", arg1, arg2) {\n+ *          prepare();\n+ *          test_something_else(arg1, arg2);\n+ *          cleanup();\n+ *  }\n+ */\n+#define if_test(...)\t\t\t\t\t\t\t\\\n+\tif (test__run_begin() ?\t\t\t\t\t\t\\\n+\t    (test__run_end(0, TEST_LOCATION(),  __VA_ARGS__), 0) :\t\\\n+\t    (test__run_describe(TEST_LOCATION(),  __VA_ARGS__), 1))\n+\n /*\n  * Print a test plan, should be called before any tests. If the number\n  * of tests is not known in advance test_done() will automatically\n@@ -153,6 +170,9 @@ union test__tmp {\n\n extern union test__tmp test__tmp[2];\n\n+__attribute__((format (printf, 2, 3)))\n+void test__run_describe(const char *, const char *, ...);\n+\n int test__run_begin(void);\n __attribute__((format (printf, 3, 4)))\n int test__run_end(int, const char *, const char *, ...);\n--\n2.46.0\n"},{"id":"499606","messageId":"d037d4eb-63ba-4d1b-b36b-7ba1eccf730f@web.de","threadId":"61700","inReplyTo":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","subject":"[PATCH v4 4/6] t-ctype: use if_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:10:09Z","receivedAt":"2024-07-30T14:10:19Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Use the documented macro if_test instead of the internal functions\ntest__run_begin() and test__run_end(), which are supposed to be private\nto the unit test framework.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-ctype.c | 4 +---\n 1 file changed, 1 insertion(+), 3 deletions(-)\n\ndiff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c\nindex d6ac1fe678..e28a7f50f9 100644\n--- a/t/unit-tests/t-ctype.c\n+++ b/t/unit-tests/t-ctype.c\n@@ -4,15 +4,13 @@\n \tsize_t len = ARRAY_SIZE(string) - 1 + \\\n \t\tBUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \\\n \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n-\tint skip = test__run_begin(); \\\n-\tif (!skip) { \\\n+\tif_test (#class \" works\") { \\\n \t\tfor (int i = 0; i < 256; i++) { \\\n \t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n \t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n \t\t} \\\n \t\tcheck(!class(EOF)); \\\n \t} \\\n-\ttest__run_end(!skip, TEST_LOCATION(), #class \" works\"); \\\n } while (0)\n\n #define DIGIT \"0123456789\"\n--\n2.46.0\n"},{"id":"499607","messageId":"a382818b-40ab-4243-9842-579b2e99022f@web.de","threadId":"61700","inReplyTo":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","subject":"[PATCH v4 5/6] t-reftable-basics: use if_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:10:59Z","receivedAt":"2024-07-30T14:11:07Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the overhead of defining and calling single-use functions by\nusing if_test instead.\n\nRun the tests in the order of definition.  We can reorder them like that\nbecause they are independent.  Technically this changes the output, but\nretains the meaning of a full run and allows for easier review e.g. with\ndiff option --ignore-all-space.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-reftable-basics.c | 228 ++++++++++++++-----------------\n 1 file changed, 106 insertions(+), 122 deletions(-)\n\ndiff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c\nindex 4e80bdf16d..1dd60ab5f0 100644\n--- a/t/unit-tests/t-reftable-basics.c\n+++ b/t/unit-tests/t-reftable-basics.c\n@@ -20,141 +20,125 @@ static int integer_needle_lesseq(size_t i, void *_args)\n \treturn args->needle <= args->haystack[i];\n }\n\n-static void test_binsearch(void)\n+int cmd_main(int argc, const char *argv[])\n {\n-\tint haystack[] = { 2, 4, 6, 8, 10 };\n-\tstruct {\n-\t\tint needle;\n-\t\tsize_t expected_idx;\n-\t} testcases[] = {\n-\t\t{-9000, 0},\n-\t\t{-1, 0},\n-\t\t{0, 0},\n-\t\t{2, 0},\n-\t\t{3, 1},\n-\t\t{4, 1},\n-\t\t{7, 3},\n-\t\t{9, 4},\n-\t\t{10, 4},\n-\t\t{11, 5},\n-\t\t{9000, 5},\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n-\t\tstruct integer_needle_lesseq_args args = {\n-\t\t\t.haystack = haystack,\n-\t\t\t.needle = testcases[i].needle,\n+\tif_test (\"binary search with binsearch works\") {\n+\t\tint haystack[] = { 2, 4, 6, 8, 10 };\n+\t\tstruct {\n+\t\t\tint needle;\n+\t\t\tsize_t expected_idx;\n+\t\t} testcases[] = {\n+\t\t\t{-9000, 0},\n+\t\t\t{-1, 0},\n+\t\t\t{0, 0},\n+\t\t\t{2, 0},\n+\t\t\t{3, 1},\n+\t\t\t{4, 1},\n+\t\t\t{7, 3},\n+\t\t\t{9, 4},\n+\t\t\t{10, 4},\n+\t\t\t{11, 5},\n+\t\t\t{9000, 5},\n \t\t};\n-\t\tsize_t idx;\n\n-\t\tidx = binsearch(ARRAY_SIZE(haystack), &integer_needle_lesseq, &args);\n-\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {\n+\t\t\tstruct integer_needle_lesseq_args args = {\n+\t\t\t\t.haystack = haystack,\n+\t\t\t\t.needle = testcases[i].needle,\n+\t\t\t};\n+\t\t\tsize_t idx;\n+\n+\t\t\tidx = binsearch(ARRAY_SIZE(haystack),\n+\t\t\t\t\t&integer_needle_lesseq, &args);\n+\t\t\tcheck_int(idx, ==, testcases[i].expected_idx);\n+\t\t}\n \t}\n-}\n\n-static void test_names_length(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", NULL };\n-\tcheck_int(names_length(a), ==, 2);\n-}\n-\n-static void test_names_equal(void)\n-{\n-\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n-\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n-\tconst char *c[] = { \"a\", \"b\", NULL };\n+\tif_test (\"names_length retuns size of a NULL-terminated string array\") {\n+\t\tconst char *a[] = { \"a\", \"b\", NULL };\n+\t\tcheck_int(names_length(a), ==, 2);\n+\t}\n\n-\tcheck(names_equal(a, a));\n-\tcheck(!names_equal(a, b));\n-\tcheck(!names_equal(a, c));\n-}\n+\tif_test (\"names_equal compares NULL-terminated string arrays\") {\n+\t\tconst char *a[] = { \"a\", \"b\", \"c\", NULL };\n+\t\tconst char *b[] = { \"a\", \"b\", \"d\", NULL };\n+\t\tconst char *c[] = { \"a\", \"b\", NULL };\n\n-static void test_parse_names_normal(void)\n-{\n-\tchar in1[] = \"line\\n\";\n-\tchar in2[] = \"a\\nb\\nc\";\n-\tchar **out = NULL;\n-\tparse_names(in1, strlen(in1), &out);\n-\tcheck_str(out[0], \"line\");\n-\tcheck(!out[1]);\n-\tfree_names(out);\n-\n-\tparse_names(in2, strlen(in2), &out);\n-\tcheck_str(out[0], \"a\");\n-\tcheck_str(out[1], \"b\");\n-\tcheck_str(out[2], \"c\");\n-\tcheck(!out[3]);\n-\tfree_names(out);\n-}\n+\t\tcheck(names_equal(a, a));\n+\t\tcheck(!names_equal(a, b));\n+\t\tcheck(!names_equal(a, c));\n+\t}\n\n-static void test_parse_names_drop_empty(void)\n-{\n-\tchar in[] = \"a\\n\\nb\\n\";\n-\tchar **out = NULL;\n-\tparse_names(in, strlen(in), &out);\n-\tcheck_str(out[0], \"a\");\n-\t/* simply '\\n' should be dropped as empty string */\n-\tcheck_str(out[1], \"b\");\n-\tcheck(!out[2]);\n-\tfree_names(out);\n-}\n+\tif_test (\"parse_names works for basic input\") {\n+\t\tchar in1[] = \"line\\n\";\n+\t\tchar in2[] = \"a\\nb\\nc\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in1, strlen(in1), &out);\n+\t\tcheck_str(out[0], \"line\");\n+\t\tcheck(!out[1]);\n+\t\tfree_names(out);\n+\n+\t\tparse_names(in2, strlen(in2), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck_str(out[2], \"c\");\n+\t\tcheck(!out[3]);\n+\t\tfree_names(out);\n+\t}\n\n-static void test_common_prefix(void)\n-{\n-\tstruct strbuf a = STRBUF_INIT;\n-\tstruct strbuf b = STRBUF_INIT;\n-\tstruct {\n-\t\tconst char *a, *b;\n-\t\tint want;\n-\t} cases[] = {\n-\t\t{\"abcdef\", \"abc\", 3},\n-\t\t{ \"abc\", \"ab\", 2 },\n-\t\t{ \"\", \"abc\", 0 },\n-\t\t{ \"abc\", \"abd\", 2 },\n-\t\t{ \"abc\", \"pqr\", 0 },\n-\t};\n-\n-\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n-\t\tstrbuf_addstr(&a, cases[i].a);\n-\t\tstrbuf_addstr(&b, cases[i].b);\n-\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n-\t\tstrbuf_reset(&a);\n-\t\tstrbuf_reset(&b);\n+\tif_test (\"parse_names drops empty string\") {\n+\t\tchar in[] = \"a\\n\\nb\\n\";\n+\t\tchar **out = NULL;\n+\t\tparse_names(in, strlen(in), &out);\n+\t\tcheck_str(out[0], \"a\");\n+\t\t/* simply '\\n' should be dropped as empty string */\n+\t\tcheck_str(out[1], \"b\");\n+\t\tcheck(!out[2]);\n+\t\tfree_names(out);\n \t}\n-\tstrbuf_release(&a);\n-\tstrbuf_release(&b);\n-}\n\n-static void test_u24_roundtrip(void)\n-{\n-\tuint32_t in = 0x112233;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be24(dest, in);\n-\tout = get_be24(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\tif_test (\"common_prefix_size works\") {\n+\t\tstruct strbuf a = STRBUF_INIT;\n+\t\tstruct strbuf b = STRBUF_INIT;\n+\t\tstruct {\n+\t\t\tconst char *a, *b;\n+\t\t\tint want;\n+\t\t} cases[] = {\n+\t\t\t{\"abcdef\", \"abc\", 3},\n+\t\t\t{ \"abc\", \"ab\", 2 },\n+\t\t\t{ \"\", \"abc\", 0 },\n+\t\t\t{ \"abc\", \"abd\", 2 },\n+\t\t\t{ \"abc\", \"pqr\", 0 },\n+\t\t};\n\n-static void test_u16_roundtrip(void)\n-{\n-\tuint32_t in = 0xfef1;\n-\tuint8_t dest[3];\n-\tuint32_t out;\n-\tput_be16(dest, in);\n-\tout = get_be16(dest);\n-\tcheck_int(in, ==, out);\n-}\n+\t\tfor (size_t i = 0; i < ARRAY_SIZE(cases); i++) {\n+\t\t\tstrbuf_addstr(&a, cases[i].a);\n+\t\t\tstrbuf_addstr(&b, cases[i].b);\n+\t\t\tcheck_int(common_prefix_size(&a, &b), ==, cases[i].want);\n+\t\t\tstrbuf_reset(&a);\n+\t\t\tstrbuf_reset(&b);\n+\t\t}\n+\t\tstrbuf_release(&a);\n+\t\tstrbuf_release(&b);\n+\t}\n\n-int cmd_main(int argc, const char *argv[])\n-{\n-\tTEST(test_common_prefix(), \"common_prefix_size works\");\n-\tTEST(test_parse_names_normal(), \"parse_names works for basic input\");\n-\tTEST(test_parse_names_drop_empty(), \"parse_names drops empty string\");\n-\tTEST(test_binsearch(), \"binary search with binsearch works\");\n-\tTEST(test_names_length(), \"names_length retuns size of a NULL-terminated string array\");\n-\tTEST(test_names_equal(), \"names_equal compares NULL-terminated string arrays\");\n-\tTEST(test_u24_roundtrip(), \"put_be24 and get_be24 work\");\n-\tTEST(test_u16_roundtrip(), \"put_be16 and get_be16 work\");\n+\tif_test (\"put_be24 and get_be24 work\") {\n+\t\tuint32_t in = 0x112233;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be24(dest, in);\n+\t\tout = get_be24(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n+\n+\tif_test (\"put_be16 and get_be16 work\") {\n+\t\tuint32_t in = 0xfef1;\n+\t\tuint8_t dest[3];\n+\t\tuint32_t out;\n+\t\tput_be16(dest, in);\n+\t\tout = get_be16(dest);\n+\t\tcheck_int(in, ==, out);\n+\t}\n\n \treturn test_done();\n }\n--\n2.46.0\n"},{"id":"499608","messageId":"b4204fa2-dc48-4ffc-beb2-54e9647cbebb@web.de","threadId":"61700","inReplyTo":"077a178e-eb30-45ff-b653-a514bfd33077@web.de","subject":"[PATCH v4 6/6] t-strvec: use if_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-30T14:12:36Z","receivedAt":"2024-07-30T14:12:45Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The macro TEST takes a single expression.  If a test requires multiple\nstatements then they need to be placed in a function that's called in\nthe TEST expression.\n\nRemove the cognitive overhead of defining and calling single-use\nfunctions by using if_test instead.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n t/unit-tests/t-strvec.c | 356 ++++++++++++++++++----------------------\n 1 file changed, 156 insertions(+), 200 deletions(-)\n\ndiff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c\nindex d4615ab06d..557e9c835c 100644\n--- a/t/unit-tests/t-strvec.c\n+++ b/t/unit-tests/t-strvec.c\n@@ -36,237 +36,193 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)\n \tcheck_pointer_eq(vec->v[nr], NULL);\n }\n\n-static void t_static_init(void)\n+int cmd_main(int argc, const char **argv)\n {\n-\tstruct strvec vec = STRVEC_INIT;\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tif_test (\"static initialization\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_dynamic_init(void)\n-{\n-\tstruct strvec vec;\n-\tstrvec_init(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tif_test (\"dynamic initialization\") {\n+\t\tstruct strvec vec;\n+\t\tstrvec_init(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_clear(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_push(&vec, \"foo\");\n-\tstrvec_clear(&vec);\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n-}\n+\tif_test (\"clear\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_clear(&vec);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n+\t}\n\n-static void t_push(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n+\tif_test (\"push\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_push(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_push(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n\n-\tstrvec_push(&vec, \"bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_push(&vec, \"bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushf(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushf(&vec, \"foo: %d\", 1);\n-\tcheck_strvec(&vec, \"foo: 1\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"pushf\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushf(&vec, \"foo: %d\", 1);\n+\t\tcheck_strvec(&vec, \"foo: 1\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushl(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"pushl\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pushv(void)\n-{\n-\tconst char *strings[] = {\n-\t\t\"foo\", \"bar\", \"baz\", NULL,\n-\t};\n-\tstruct strvec vec = STRVEC_INIT;\n+\tif_test (\"pushv\") {\n+\t\tconst char *strings[] = {\n+\t\t\t\"foo\", \"bar\", \"baz\", NULL,\n+\t\t};\n+\t\tstruct strvec vec = STRVEC_INIT;\n\n-\tstrvec_pushv(&vec, strings);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pushv(&vec, strings);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n\n-\tstrvec_clear(&vec);\n-}\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 0, \"replaced\");\n-\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"replace at head\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 0, \"replaced\");\n+\t\tcheck_strvec(&vec, \"replaced\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 2, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"replace at tail\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 2, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"replaced\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_replace(&vec, 1, \"replaced\");\n-\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"replace in between\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_replace(&vec, 1, \"replaced\");\n+\t\tcheck_strvec(&vec, \"foo\", \"replaced\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_replace_with_substring(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", NULL);\n-\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n-\tcheck_strvec(&vec, \"oo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"replace with substring\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", NULL);\n+\t\tstrvec_replace(&vec, 0, vec.v[0] + 1);\n+\t\tcheck_strvec(&vec, \"oo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_head(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 0);\n-\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"remove at head\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 0);\n+\t\tcheck_strvec(&vec, \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_at_tail(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 2);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"remove at tail\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 2);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_remove_in_between(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_remove(&vec, 1);\n-\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"remove in between\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_remove(&vec, 1);\n+\t\tcheck_strvec(&vec, \"foo\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"pop with empty array\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_pop_non_empty_array(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_pop(&vec);\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"pop with non-empty array\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_pushl(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_pop(&vec);\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_empty_string(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"split empty string\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_single_item(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\");\n-\tcheck_strvec(&vec, \"foo\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"split single item\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\");\n+\t\tcheck_strvec(&vec, \"foo\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_items(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo bar baz\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"split multiple items\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo bar baz\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", \"baz\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_whitespace_only(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \" \\t\\n\");\n-\tcheck_strvec(&vec, NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"split whitespace only\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \" \\t\\n\");\n+\t\tcheck_strvec(&vec, NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_split_multiple_consecutive_whitespaces(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tstrvec_split(&vec, \"foo\\n\\t bar\");\n-\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n-\tstrvec_clear(&vec);\n-}\n+\tif_test (\"split multiple consecutive whitespaces\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tstrvec_split(&vec, \"foo\\n\\t bar\");\n+\t\tcheck_strvec(&vec, \"foo\", \"bar\", NULL);\n+\t\tstrvec_clear(&vec);\n+\t}\n\n-static void t_detach(void)\n-{\n-\tstruct strvec vec = STRVEC_INIT;\n-\tconst char **detached;\n+\tif_test (\"detach\") {\n+\t\tstruct strvec vec = STRVEC_INIT;\n+\t\tconst char **detached;\n\n-\tstrvec_push(&vec, \"foo\");\n+\t\tstrvec_push(&vec, \"foo\");\n\n-\tdetached = strvec_detach(&vec);\n-\tcheck_str(detached[0], \"foo\");\n-\tcheck_pointer_eq(detached[1], NULL);\n+\t\tdetached = strvec_detach(&vec);\n+\t\tcheck_str(detached[0], \"foo\");\n+\t\tcheck_pointer_eq(detached[1], NULL);\n\n-\tcheck_pointer_eq(vec.v, empty_strvec);\n-\tcheck_uint(vec.nr, ==, 0);\n-\tcheck_uint(vec.alloc, ==, 0);\n+\t\tcheck_pointer_eq(vec.v, empty_strvec);\n+\t\tcheck_uint(vec.nr, ==, 0);\n+\t\tcheck_uint(vec.alloc, ==, 0);\n\n-\tfree((char *) detached[0]);\n-\tfree(detached);\n-}\n+\t\tfree((char *) detached[0]);\n+\t\tfree(detached);\n+\t}\n\n-int cmd_main(int argc, const char **argv)\n-{\n-\tTEST(t_static_init(), \"static initialization\");\n-\tTEST(t_dynamic_init(), \"dynamic initialization\");\n-\tTEST(t_clear(), \"clear\");\n-\tTEST(t_push(), \"push\");\n-\tTEST(t_pushf(), \"pushf\");\n-\tTEST(t_pushl(), \"pushl\");\n-\tTEST(t_pushv(), \"pushv\");\n-\tTEST(t_replace_at_head(), \"replace at head\");\n-\tTEST(t_replace_in_between(), \"replace in between\");\n-\tTEST(t_replace_at_tail(), \"replace at tail\");\n-\tTEST(t_replace_with_substring(), \"replace with substring\");\n-\tTEST(t_remove_at_head(), \"remove at head\");\n-\tTEST(t_remove_in_between(), \"remove in between\");\n-\tTEST(t_remove_at_tail(), \"remove at tail\");\n-\tTEST(t_pop_empty_array(), \"pop with empty array\");\n-\tTEST(t_pop_non_empty_array(), \"pop with non-empty array\");\n-\tTEST(t_split_empty_string(), \"split empty string\");\n-\tTEST(t_split_single_item(), \"split single item\");\n-\tTEST(t_split_multiple_items(), \"split multiple items\");\n-\tTEST(t_split_whitespace_only(), \"split whitespace only\");\n-\tTEST(t_split_multiple_consecutive_whitespaces(), \"split multiple consecutive whitespaces\");\n-\tTEST(t_detach(), \"detach\");\n \treturn test_done();\n }\n--\n2.46.0\n"},{"id":"499665","messageId":"ZqnJcawtpzvguxtq@tanuki","threadId":"61700","inReplyTo":"53c1eaeb-6106-492c-9c44-13a7cd1bd0d0@web.de","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-07-31T05:19:45Z","receivedAt":"2024-07-31T05:19:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jul 30, 2024 at 04:00:07PM +0200, René Scharfe wrote:\n> Am 30.07.24 um 06:49 schrieb Patrick Steinhardt:\n> > On Mon, Jul 29, 2024 at 11:55:52AM -0700, Junio C Hamano wrote:\n> >> Patrick Steinhardt <ps@pks.im> writes:\n> >>\n> >>> is standalone nowadays and called \"clar\" [1]. The biggest downside of it\n> >>> is that it depends on Python to auto-generate the test \"main\" function,\n> >>> which is not really a good fit for the Git project.\n> >>\n> >> Is that because Python is optional (like, we only use it for\n> >> optional Perforce thing and import-zip in contrib), or are there\n> >> other concerns?\n> >>\n> >> Unlike these components, unit tests are not even for the end-user\n> >> consumers, so if it is Python that you find it a blocker, I do not\n> >> see a reason to reject it.  The thing looked like a simple script\n> >> that does not use any tricky language construct or modules.\n> >\n> > No concerns other than adding another mandatory dependency for devs. We\n> > already depend on Perl and Shell, so adding Python to the stack feels\n> > suboptimal to me.\n> >\n> > As you say though, the script isn't all that involved. So it would also\n> > be possible to port it to Perl if we want to do that, I guess.\n> \n> From the point of view of a \"minimal C unit testing framework\" surely\n> the implementation language with the best dependency story would be C.\n> Perhaps it could then also test itself.  On the other hand, writing a\n> string handling program in 2024 in C is probably not the smartest idea.\n\nI certainly wouldn't want to do it ;) I think Perl would be perfectly\nfine given that we already rely on it rather extensively in Git.\n\n> Then again, generate.py uses a non-greedy regex to remove single-line\n> comments, which seems wrong, and doesn't seem to support preprocessor\n> directives like #if and #ifdef, so whole tests can't be disabled this\n> way.\n\nI guess it doesn't have to be perfect, just good enough. clar comes with\nsome niceties like being able to run only some tests, all tests of a\nparticular suite or to exclude certain tests:\n\n    # Run only submodule::add tests.\n    $ ./libgit2_tests -t -ssubmodule::add::\n    TAP version 13\n    # start of suite 1: submodule::add\n    ok 1 - submodule::add::url_absolute\n    ok 2 - submodule::add::url_relative\n    ok 3 - submodule::add::url_relative_to_origin\n    ok 4 - submodule::add::url_relative_to_workdir\n    ok 5 - submodule::add::path_exists_in_index\n    ok 6 - submodule::add::file_exists_in_index\n    ok 7 - submodule::add::submodule_clone\n    ok 8 - submodule::add::submodule_clone_into_nonempty_dir_succeeds\n    ok 9 - submodule::add::submodule_clone_twice_fails\n    1..9\n\n    # Run only a single test in the submodule::add suite.\n    $ ./libgit2_tests -t -ssubmodule::add::url_absolute\n    TAP version 13\n    # start of suite 1: submodule::add\n    ok 1 - submodule::add::url_absolute\n    1..1\n\n    # Run all submodule tests, but exclude submodule::add.\n    $ ./libgit2_tests -t -ssubmodule::  -xsubmodule::add::\n    TAP version 13\n    # start of suite 1: submodule::escape\n    ok 1 - submodule::escape::from_gitdir\n    ok 2 - submodule::escape::from_gitdir_windows\n    # start of suite 2: submodule::init\n    ok 3 - submodule::init::absolute_url\n    ...\n\nSo I'm not sure whether it's all that important to be able to recognize\nthings like #if and #ifdef given that you can pick tests to run on the\ncommand line.\n\nThe alternative would be of course to hook up LLVM and properly parse\nthe C sources. But that feels like overthinking it ;)\n\n> And it uses pickle to cache results; does that mean it would be\n> slow without it?\n\nGood question. I plan to find some time this week to draft a PoC and\nwill remember to benchmark this.\n\nPatrick\n"},{"id":"499722","messageId":"85c4f785-de27-488c-aace-c93c2c2a84ac@web.de","threadId":"61700","inReplyTo":"ZqnJcawtpzvguxtq@tanuki","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-07-31T16:48:33Z","receivedAt":"2024-07-31T16:48:56Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 31.07.24 um 07:19 schrieb Patrick Steinhardt:\n> On Tue, Jul 30, 2024 at 04:00:07PM +0200, René Scharfe wrote:\n>>\n>> From the point of view of a \"minimal C unit testing framework\" surely\n>> the implementation language with the best dependency story would be C.\n>> Perhaps it could then also test itself.  On the other hand, writing a\n>> string handling program in 2024 in C is probably not the smartest idea.\n>\n> I certainly wouldn't want to do it ;) I think Perl would be perfectly\n> fine given that we already rely on it rather extensively in Git.\n\nRight.\n\n> clar comes with\n> some niceties like being able to run only some tests, all tests of a\n> particular suite or to exclude certain tests:\n>\n>     # Run only submodule::add tests.\n>     $ ./libgit2_tests -t -ssubmodule::add::\n>     TAP version 13\n>     # start of suite 1: submodule::add\n>     ok 1 - submodule::add::url_absolute\n>     ok 2 - submodule::add::url_relative\n>     ok 3 - submodule::add::url_relative_to_origin\n>     ok 4 - submodule::add::url_relative_to_workdir\n>     ok 5 - submodule::add::path_exists_in_index\n>     ok 6 - submodule::add::file_exists_in_index\n>     ok 7 - submodule::add::submodule_clone\n>     ok 8 - submodule::add::submodule_clone_into_nonempty_dir_succeeds\n>     ok 9 - submodule::add::submodule_clone_twice_fails\n>     1..9\n>\n>     # Run only a single test in the submodule::add suite.\n>     $ ./libgit2_tests -t -ssubmodule::add::url_absolute\n>     TAP version 13\n>     # start of suite 1: submodule::add\n>     ok 1 - submodule::add::url_absolute\n>     1..1\n>\n>     # Run all submodule tests, but exclude submodule::add.\n>     $ ./libgit2_tests -t -ssubmodule::  -xsubmodule::add::\n>     TAP version 13\n>     # start of suite 1: submodule::escape\n>     ok 1 - submodule::escape::from_gitdir\n>     ok 2 - submodule::escape::from_gitdir_windows\n>     # start of suite 2: submodule::init\n>     ok 3 - submodule::init::absolute_url\n>     ...\n>\n> So I'm not sure whether it's all that important to be able to recognize\n> things like #if and #ifdef given that you can pick tests to run on the\n> command line.\n\nMy only reference are the regular (shell-based) tests, and those have\nto deal with prerequisites like PTHREADS, PCRE or LIBCURL.  Such tests\nwouldn't even compile, and would thus be #if'd out.  We'd have to move\nthose into separate files instead and skip the whole file, it seems.\n\n> The alternative would be of course to hook up LLVM and properly parse\n> the C sources. But that feels like overthinking it ;)\n\nIt's as simple as calling clang (or gcc) with the option -E.  We'd need\nto disable -Wmissing-prototypes as well, though, as there will be no\ndeclarations before we generate clar-decls.h.  Not sure how to do that\nall in a portable way.\n\nAnyway, another consequence is that adding, removing or renaming a\nsingle test function requires rebuilding all unit tests, because\nclar-decls.h changes and all of them include it.  Not a problem right\nnow.  If we ever get to the same number of test files as in the regular\ntest suite (ca. 1000) then this could become annoying.\n\nApropos, I wonder how a Clar-style t-ctype would look like, but didn't\ndare to even start thinking about it, yet.\n\nRené\n"},{"id":"499742","messageId":"CAO_smVgvUksJEk3Q3DJbB4-QbTD48o6pAONwH8Q5Hz-SVr8PjQ@mail.gmail.com","threadId":"61700","inReplyTo":"23498c37-1e30-45a9-92ae-d31a05778fca@web.de","subject":"Re: [PATCH v4 1/6] t0080: use here-doc test body","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-31T20:52:03Z","receivedAt":"2024-07-31T20:52:21Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Tue, Jul 30, 2024 at 7:06 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> Improve the readability of the expected output by using a here-doc for\n> the test body and replacing the unwieldy ${SQ} references with literal\n> single quotes.\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  t/t0080-unit-test-output.sh | 20 ++++++++++----------\n>  1 file changed, 10 insertions(+), 10 deletions(-)\n\nI'm not an expert on git's style guide or testing infrastructure, but\nthis looks like a good improvement to me. Thanks!\n"},{"id":"499744","messageId":"CAO_smVgs1+os+Mh+c_tEoD_W6m+oJG-w1QQZ8YSuzHGeWkmfcQ@mail.gmail.com","threadId":"61700","inReplyTo":"4b27cee7-98eb-41ac-a68b-44f42e15a5d2@web.de","subject":"Re: [PATCH v4 2/6] unit-tests: show location of checks outside of tests","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-31T21:03:31Z","receivedAt":"2024-07-31T21:03:57Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Tue, Jul 30, 2024 at 7:07 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> Checks outside of tests are caught at runtime and reported like this:\n>\n>  Assertion failed: (ctx.running), function test_assert, file test-lib.c, line 267.\n>\n> The assert() call aborts the unit test and doesn't reveal the location\n> or even the type of the offending check, as test_assert() is called by\n> all of them.\n>\n> Handle it like the opposite case, a test without any checks: Don't\n> abort, but report the location of the actual check, along with a message\n> explaining the situation.  The output for example above becomes:\n>\n>  # BUG: check outside of test at t/helper/test-example-tap.c:75\n>\n> ... and the unit test program continues and indicates the error in its\n> exit code at the end.\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  t/helper/test-example-tap.c | 2 ++\n>  t/t0080-unit-test-output.sh | 5 +++--\n>  t/unit-tests/test-lib.c     | 7 ++++++-\n>  3 files changed, 11 insertions(+), 3 deletions(-)\n>\n> diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\n> index d072ad559f..79c12b01cd 100644\n> --- a/t/helper/test-example-tap.c\n> +++ b/t/helper/test-example-tap.c\n> @@ -72,6 +72,8 @@ static void t_empty(void)\n>\n>  int cmd__example_tap(int argc, const char **argv)\n>  {\n> +       check(1);\n\nLet's include a comment that describes why we have this outside of the\nTEST() macros so that people don't try to \"fix\" it, and so that people\nrealize it's not meant to be a _good_ example :)\n\n> +\n>         test_res = TEST(check_res = check_int(1, ==, 1), \"passing test\");\n>         TEST(t_res(1), \"passing test and assertion return 1\");\n>         test_res = TEST(check_res = check_int(1, ==, 2), \"failing test\");\n> diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\n> index 9ec47b7360..fe221f3bdb 100755\n> --- a/t/t0080-unit-test-output.sh\n> +++ b/t/t0080-unit-test-output.sh\n> @@ -7,9 +7,10 @@ TEST_PASSES_SANITIZE_LEAK=true\n>\n>  test_expect_success 'TAP output from unit tests' - <<\\EOT\n>         cat >expect <<-EOF &&\n> +       # BUG: check outside of test at t/helper/test-example-tap.c:75\n>         ok 1 - passing test\n>         ok 2 - passing test and assertion return 1\n> -       # check \"1 == 2\" failed at t/helper/test-example-tap.c:77\n> +       # check \"1 == 2\" failed at t/helper/test-example-tap.c:79\n>         #    left: 1\n>         #   right: 2\n>         not ok 3 - failing test\n> @@ -46,7 +47,7 @@ test_expect_success 'TAP output from unit tests' - <<\\EOT\n>         #    left: '\\\\\\\\'\n>         #   right: '\\\\''\n>         not ok 17 - messages from failing string and char comparison\n> -       # BUG: test has no checks at t/helper/test-example-tap.c:92\n> +       # BUG: test has no checks at t/helper/test-example-tap.c:94\n>         not ok 18 - test with no checks\n>         ok 19 - test with no checks returns 0\n>         1..19\n> diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\n> index 3c513ce59a..989dc758e6 100644\n> --- a/t/unit-tests/test-lib.c\n> +++ b/t/unit-tests/test-lib.c\n> @@ -264,7 +264,12 @@ static void test_todo(void)\n>\n>  int test_assert(const char *location, const char *check, int ok)\n>  {\n> -       assert(ctx.running);\n> +       if (!ctx.running) {\n> +               test_msg(\"BUG: check outside of test at %s\",\n> +                        make_relative(location));\n\nBelow, `test_msg` emits a message like `skipping check '1 == 2' at\n<loc>`. Should we include 'check' as part of the message here, or is\nit not possible or not useful for some reason?\n\n> +               ctx.failed = 1;\n> +               return 0;\n> +       }\n>\n>         if (ctx.result == RESULT_SKIP) {\n>                 test_msg(\"skipping check '%s' at %s\", check,\n> --\n> 2.46.0\n"},{"id":"499750","messageId":"CAO_smVh_2FEbTAqJDMZAKMsfTKrB=NH36G6jNmMTaEOtRaP2Pg@mail.gmail.com","threadId":"61700","inReplyTo":"da902de9-288e-4783-8b4b-a2a968d1e1f8@web.de","subject":"Re: [PATCH v4 3/6] unit-tests: add if_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-07-31T22:04:25Z","receivedAt":"2024-07-31T22:04:44Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Tue, Jul 30, 2024 at 7:08 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> The macro TEST only allows defining a test that consists of a single\n> expression.  Add a new macro, if_test, which provides a way to define\n> unit tests that are made up of one or more statements.\n>\n> if_test allows defining self-contained tests en bloc, a bit like\n> test_expect_success does for regular tests.  It acts like a conditional;\n> the test body is executed if test_skip_all() had not been called before.\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  .clang-format               |  5 +++++\n>  t/helper/test-example-tap.c | 33 +++++++++++++++++++++++++++++++++\n>  t/t0080-unit-test-output.sh | 35 ++++++++++++++++++++++++++++++++++-\n>  t/unit-tests/test-lib.c     | 29 +++++++++++++++++++++++++++++\n>  t/unit-tests/test-lib.h     | 20 ++++++++++++++++++++\n>  5 files changed, 121 insertions(+), 1 deletion(-)\n>\n> diff --git a/.clang-format b/.clang-format\n> index 6408251577..4c6d317508 100644\n> --- a/.clang-format\n> +++ b/.clang-format\n> @@ -169,6 +169,11 @@ ForEachMacros:\n>    - 'strmap_for_each_entry'\n>    - 'strset_for_each_entry'\n>\n> +# A list of macros that should be interpreted as conditionals instead of as\n> +# function calls.\n> +IfMacros:\n> +  - 'if_test'\n> +\n>  # The maximum number of consecutive empty lines to keep.\n>  MaxEmptyLinesToKeep: 1\n>\n> diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\n> index 79c12b01cd..914af88e0a 100644\n> --- a/t/helper/test-example-tap.c\n> +++ b/t/helper/test-example-tap.c\n> @@ -94,5 +94,38 @@ int cmd__example_tap(int argc, const char **argv)\n>         test_res = TEST(t_empty(), \"test with no checks\");\n>         TEST(check_int(test_res, ==, 0), \"test with no checks returns 0\");\n>\n> +       if_test (\"if_test passing test\")\n> +               check_int(1, ==, 1);\n> +       if_test (\"if_test failing test\")\n> +               check_int(1, ==, 2);\n> +       if_test (\"if_test passing TEST_TODO()\")\n> +               TEST_TODO(check(0));\n> +       if_test (\"if_test failing TEST_TODO()\")\n> +               TEST_TODO(check(1));\n> +       if_test (\"if_test test_skip()\") {\n> +               check(0);\n> +               test_skip(\"missing prerequisite\");\n> +               check(1);\n> +       }\n> +       if_test (\"if_test test_skip() inside TEST_TODO()\")\n> +               TEST_TODO((test_skip(\"missing prerequisite\"), 1));\n> +       if_test (\"if_test TEST_TODO() after failing check\") {\n> +               check(0);\n> +               TEST_TODO(check(0));\n> +       }\n> +       if_test (\"if_test failing check after TEST_TODO()\") {\n> +               check(1);\n> +               TEST_TODO(check(0));\n> +               check(0);\n> +       }\n> +       if_test (\"if_test messages from failing string and char comparison\") {\n> +               check_str(\"\\thello\\\\\", \"there\\\"\\n\");\n> +               check_str(\"NULL\", NULL);\n> +               check_char('a', ==, '\\n');\n> +               check_char('\\\\', ==, '\\'');\n> +       }\n> +       if_test (\"if_test test with no checks\")\n> +               ; /* nothing */\n> +\n>         return test_done();\n>  }\n> diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh\n> index fe221f3bdb..3c369c88e2 100755\n> --- a/t/t0080-unit-test-output.sh\n> +++ b/t/t0080-unit-test-output.sh\n> @@ -50,7 +50,40 @@ test_expect_success 'TAP output from unit tests' - <<\\EOT\n>         # BUG: test has no checks at t/helper/test-example-tap.c:94\n>         not ok 18 - test with no checks\n>         ok 19 - test with no checks returns 0\n> -       1..19\n> +       ok 20 - if_test passing test\n> +       # check \"1 == 2\" failed at t/helper/test-example-tap.c:100\n> +       #    left: 1\n> +       #   right: 2\n> +       not ok 21 - if_test failing test\n> +       not ok 22 - if_test passing TEST_TODO() # TODO\n> +       # todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104\n> +       not ok 23 - if_test failing TEST_TODO()\n> +       # check \"0\" failed at t/helper/test-example-tap.c:106\n> +       # skipping test - missing prerequisite\n> +       # skipping check '1' at t/helper/test-example-tap.c:108\n> +       ok 24 - if_test test_skip() # SKIP\n> +       # skipping test - missing prerequisite\n> +       ok 25 - if_test test_skip() inside TEST_TODO() # SKIP\n> +       # check \"0\" failed at t/helper/test-example-tap.c:113\n> +       not ok 26 - if_test TEST_TODO() after failing check\n> +       # check \"0\" failed at t/helper/test-example-tap.c:119\n> +       not ok 27 - if_test failing check after TEST_TODO()\n> +       # check \"!strcmp(\"\\thello\\\\\\\\\", \"there\\\"\\n\")\" failed at t/helper/test-example-tap.c:122\n> +       #    left: \"\\011hello\\\\\\\\\"\n> +       #   right: \"there\\\"\\012\"\n> +       # check \"!strcmp(\"NULL\", NULL)\" failed at t/helper/test-example-tap.c:123\n> +       #    left: \"NULL\"\n> +       #   right: NULL\n> +       # check \"'a' == '\\n'\" failed at t/helper/test-example-tap.c:124\n> +       #    left: 'a'\n> +       #   right: '\\012'\n> +       # check \"'\\\\\\\\' == '\\\\''\" failed at t/helper/test-example-tap.c:125\n> +       #    left: '\\\\\\\\'\n> +       #   right: '\\\\''\n> +       not ok 28 - if_test messages from failing string and char comparison\n> +       # BUG: test has no checks at t/helper/test-example-tap.c:127\n> +       not ok 29 - if_test test with no checks\n> +       1..29\n>         EOF\n>\n>         ! test-tool example-tap >actual &&\n> diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\n> index 989dc758e6..fa1f95965c 100644\n> --- a/t/unit-tests/test-lib.c\n> +++ b/t/unit-tests/test-lib.c\n> @@ -16,6 +16,8 @@ static struct {\n>         unsigned running :1;\n>         unsigned skip_all :1;\n>         unsigned todo :1;\n> +       char location[100];\n> +       char description[100];\n>  } ctx = {\n>         .lazy_plan = 1,\n>         .result = RESULT_NONE,\n> @@ -125,6 +127,8 @@ void test_plan(int count)\n>\n>  int test_done(void)\n>  {\n> +       if (ctx.running && ctx.location[0] && ctx.description[0])\n> +               test__run_end(1, ctx.location, \"%s\", ctx.description);\n>         assert(!ctx.running);\n>\n>         if (ctx.lazy_plan)\n> @@ -167,13 +171,38 @@ void test_skip_all(const char *format, ...)\n>         va_end(ap);\n>  }\n>\n> +void test__run_describe(const char *location, const char *format, ...)\n> +{\n> +       va_list ap;\n> +       int len;\n> +\n> +       assert(ctx.running);\n> +       assert(!ctx.location[0]);\n> +       assert(!ctx.description[0]);\n> +\n> +       xsnprintf(ctx.location, sizeof(ctx.location), \"%s\",\n> +                 make_relative(location));\n> +\n> +       va_start(ap, format);\n> +       len = vsnprintf(ctx.description, sizeof(ctx.description), format, ap);\n> +       va_end(ap);\n> +       if (len < 0)\n> +               die(\"unable to format message: %s\", format);\n> +       if (len >= sizeof(ctx.description))\n> +               BUG(\"ctx.description too small to format %s\", format);\n> +}\n> +\n>  int test__run_begin(void)\n>  {\n> +       if (ctx.running && ctx.location[0] && ctx.description[0])\n> +               test__run_end(1, ctx.location, \"%s\", ctx.description);\n>         assert(!ctx.running);\n>\n>         ctx.count++;\n>         ctx.result = RESULT_NONE;\n>         ctx.running = 1;\n> +       ctx.location[0] = '\\0';\n> +       ctx.description[0] = '\\0';\n>\n>         return ctx.skip_all;\n>  }\n> diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h\n> index 2de6d715d5..f15dceb29e 100644\n> --- a/t/unit-tests/test-lib.h\n> +++ b/t/unit-tests/test-lib.h\n> @@ -14,6 +14,23 @@\n>         test__run_end(test__run_begin() ? 0 : (t, 1),   \\\n>                       TEST_LOCATION(),  __VA_ARGS__)\n>\n> +/*\n> + * Run a test unless test_skip_all() has been called.  Acts like a\n> + * conditional; the test body is expected as a statement or block after\n> + * the closing parenthesis.  The description for each test should be\n> + * unique.  E.g.:\n> + *\n> + *  if_test (\"something else %d %d\", arg1, arg2) {\n> + *          prepare();\n> + *          test_something_else(arg1, arg2);\n> + *          cleanup();\n> + *  }\n> + */\n> +#define if_test(...)                                                   \\\n> +       if (test__run_begin() ?                                         \\\n> +           (test__run_end(0, TEST_LOCATION(),  __VA_ARGS__), 0) :      \\\n> +           (test__run_describe(TEST_LOCATION(),  __VA_ARGS__), 1))\n> +\n\nI personally prefer this implementation much more than the `for_test`\nimplementation, thanks. I think the extra functionality/usability from\nhaving `test__run_end` automatically called is also very useful.\n\nI'm still not wild about the name, and I'm still not convinced of the\nreadability improvements. I still want to read `if_test(...)` as\nhaving a condition in the parenthesis. Tests \"skipping everything\"\nseems like an internal implementation detail that there's disagreement\non whether to expose to the test author/reader or not; maybe we should\nredesign if we don't migrate to clar or some other test framework. Do\nwe get significant benefits from a test function continuing to execute\nafter the \"skip everything\" flag has been set?\n\nSpecifically, the only current user of `test_skip_all` is t-strbuf.c.\nMaybe we do this in t-strbuf.c, and remove the \"skip everything\"\nlogic? Below, `test_skipping_remainder` does most of the stuff that\n`test_skip_all` does, except:\n- automatically calls test_done()\n- \"poison\"s some state so that we can't call `test__run_begin`\nanymore, if test author forgets to `return` from this\n\n  int cmd_main(int argc, const char **argv)\n  {\n    if (!TEST(t_static_init(), \"static initialization works\"))\n-     test_skip_all(\"STRBUF_INIT is broken\");\n+    return test_skipping_remainder(\"STRBUF_INIT is broken\");\n    TEST(t_dynamic_init(), \"dynamic initialization works\");\n    TEST(setup(t_addch, \"a\"), \"strbuf_addch adds char\");\n    TEST(setup(t_addch, \"\"), \"strbuf_addch adds NUL char\");\n    TEST(setup_populated(t_addch, \"initial value\", \"a\"),\n         \"strbuf_addch appends to initial value\");\n    TEST(setup(t_addstr, \"hello there\"), \"strbuf_addstr adds string\");\n    TEST(setup_populated(t_addstr, \"initial value\", \"hello there\"),\n         \"strbuf_addstr appends string to initial value\");\n\n    return test_done();\n  }\n\nWith that out of the way, I think that `test__run_begin()` always\nreturns true, and calling `test__run_end()` isn't necessary, so this\nwould be able to become:\n\n#define if_test(...) \\\n    if (test__run_describe(TEST_LOCATION(), __VA_ARGS__), 1)  /*\nautomatically calls test__run_begin() */\n\nAt that point, the `if` is just used to allow this syntax:\n\nif_test(\"example if_test\") {\n    check_int(1, ==, 1);\n    check_int(1, !=, 2);\n}\n\nBut it's equivalent, since we don't have any conditional that we're\nactually checking, to:\n\n{\n    if_test(\"example if_test\");  /* note the semicolon; leaving it off\ndoesn't matter, as long as compiler doesn't complain about empty if\nblock */\n    check_int(1, ==, 1);\n    check_int(1, !=, 2);\n}\n\nOr even doing that without the {}. At that point, we can remove the\n`if` aspect of the name, and get something like this, where you can\nuse {} blocks if needed or desired:\n\n#define multiline_test(...) test__run_describe(TEST_LOCATION(), __VA_ARGS__)\n\nmultiline_test(\"equality\");  /* semicolon required */\ncheck_int(1, ==, 1);\ncheck_int(2, ==, 2);\nmultiline_test(\"inequality\");\ncheck_int(1, !=, 2);\ncheck_int(2, !=, 1);\n\n\n>  /*\n>   * Print a test plan, should be called before any tests. If the number\n>   * of tests is not known in advance test_done() will automatically\n> @@ -153,6 +170,9 @@ union test__tmp {\n>\n>  extern union test__tmp test__tmp[2];\n>\n> +__attribute__((format (printf, 2, 3)))\n> +void test__run_describe(const char *, const char *, ...);\n> +\n>  int test__run_begin(void);\n>  __attribute__((format (printf, 3, 4)))\n>  int test__run_end(int, const char *, const char *, ...);\n> --\n> 2.46.0\n"},{"id":"499768","messageId":"Zqswi3tmX9NCF0zI@tanuki","threadId":"61700","inReplyTo":"85c4f785-de27-488c-aace-c93c2c2a84ac@web.de","subject":"Re: [PATCH v3 3/7] unit-tests: add for_test","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-08-01T06:51:55Z","receivedAt":"2024-08-01T06:52:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Jul 31, 2024 at 06:48:33PM +0200, René Scharfe wrote:\n> Am 31.07.24 um 07:19 schrieb Patrick Steinhardt:\n> > On Tue, Jul 30, 2024 at 04:00:07PM +0200, René Scharfe wrote:\n> Apropos, I wonder how a Clar-style t-ctype would look like, but didn't\n> dare to even start thinking about it, yet.\n\nOh, I already had it converted locally, but didn't end up sending it out\nto keep things focussed for now. Below diff is how it could look like.\n\nPatrick\n\ndiff --git a/Makefile b/Makefile\nindex cfdce5770b..ed67feb32e 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1332,13 +1332,13 @@ THIRD_PARTY_SOURCES += compat/regex/%\n THIRD_PARTY_SOURCES += sha1collisiondetection/%\n THIRD_PARTY_SOURCES += sha1dc/%\n \n+UNIT_TESTS_SUITES += ctype\n UNIT_TESTS_SUITES += strvec\n UNIT_TESTS_PROG = $(UNIT_TEST_BIN)/unit-tests$X\n UNIT_TESTS_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TESTS_SUITES))\n UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/clar/clar.o\n UNIT_TESTS_OBJS += $(UNIT_TEST_DIR)/unit-test.o\n \n-UNIT_TEST_PROGRAMS += t-ctype\n UNIT_TEST_PROGRAMS += t-example-decorate\n UNIT_TEST_PROGRAMS += t-hash\n UNIT_TEST_PROGRAMS += t-mem-pool\ndiff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/ctype.c\nsimilarity index 70%\nrename from t/unit-tests/t-ctype.c\nrename to t/unit-tests/ctype.c\nindex d6ac1fe678..2f6696e828 100644\n--- a/t/unit-tests/t-ctype.c\n+++ b/t/unit-tests/ctype.c\n@@ -1,18 +1,12 @@\n-#include \"test-lib.h\"\n+#include \"unit-test.h\"\n \n #define TEST_CHAR_CLASS(class, string) do { \\\n \tsize_t len = ARRAY_SIZE(string) - 1 + \\\n \t\tBUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \\\n \t\tBUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \\\n-\tint skip = test__run_begin(); \\\n-\tif (!skip) { \\\n-\t\tfor (int i = 0; i < 256; i++) { \\\n-\t\t\tif (!check_int(class(i), ==, !!memchr(string, i, len)))\\\n-\t\t\t\ttest_msg(\"      i: 0x%02x\", i); \\\n-\t\t} \\\n-\t\tcheck(!class(EOF)); \\\n-\t} \\\n-\ttest__run_end(!skip, TEST_LOCATION(), #class \" works\"); \\\n+\tfor (int i = 0; i < 256; i++) \\\n+\t\tcl_assert_equal_i(class(i), !!memchr(string, i, len)); \\\n+\tcl_assert(!class(EOF)); \\\n } while (0)\n \n #define DIGIT \"0123456789\"\n@@ -33,21 +27,72 @@\n \t\"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f\" \\\n \t\"\\x7f\"\n \n-int cmd_main(int argc, const char **argv) {\n+void test_ctype__isspace(void)\n+{\n \tTEST_CHAR_CLASS(isspace, \" \\n\\r\\t\");\n+}\n+\n+void test_TEST_CHAR_ctype__CLASSisdigit(void)\n+{\n \tTEST_CHAR_CLASS(isdigit, DIGIT);\n+}\n+\n+void test_TEST_CHAR_ctype__CLASSisalpha(void)\n+{\n \tTEST_CHAR_CLASS(isalpha, LOWER UPPER);\n+}\n+\n+void test_ctype__isalnum(void)\n+{\n \tTEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT);\n+}\n+\n+void test_is_glob_ctype__special(void)\n+{\n \tTEST_CHAR_CLASS(is_glob_special, \"*?[\\\\\");\n+}\n+\n+void test_is_regex_ctype__special(void)\n+{\n \tTEST_CHAR_CLASS(is_regex_special, \"$()*+.?[\\\\^{|\");\n+}\n+\n+void test_is_pathspec_ctype__magic(void)\n+{\n \tTEST_CHAR_CLASS(is_pathspec_magic, \"!\\\"#%&',-/:;<=>@_`~\");\n+}\n+\n+void test_ctype__isascii(void)\n+{\n \tTEST_CHAR_CLASS(isascii, ASCII);\n+}\n+\n+void test_ctype__islower(void)\n+{\n \tTEST_CHAR_CLASS(islower, LOWER);\n+}\n+\n+void test_ctype__isupper(void)\n+{\n \tTEST_CHAR_CLASS(isupper, UPPER);\n+}\n+\n+void test_ctype__iscntrl(void)\n+{\n \tTEST_CHAR_CLASS(iscntrl, CNTRL);\n+}\n+\n+void test_ctype__ispunct(void)\n+{\n \tTEST_CHAR_CLASS(ispunct, PUNCT);\n+}\n+\n+void test_ctype__isxdigit(void)\n+{\n \tTEST_CHAR_CLASS(isxdigit, DIGIT \"abcdefABCDEF\");\n-\tTEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT \" \");\n+}\n \n-\treturn test_done();\n+void test_ctype__isprint(void)\n+{\n+\tTEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT \" \");\n }\n\n"},{"id":"499769","messageId":"1970186f-f9f7-4cb7-a50e-fcf91da7ea9d@web.de","threadId":"61700","inReplyTo":"CAO_smVgs1+os+Mh+c_tEoD_W6m+oJG-w1QQZ8YSuzHGeWkmfcQ@mail.gmail.com","subject":"Re: [PATCH v4 2/6] unit-tests: show location of checks outside of tests","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-08-01T07:23:52Z","receivedAt":"2024-08-01T07:24:01Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 31.07.24 um 23:03 schrieb Kyle Lippincott:\n> On Tue, Jul 30, 2024 at 7:07 AM René Scharfe <l.s.r@web.de> wrote:\n>>\n>> diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c\n>> index d072ad559f..79c12b01cd 100644\n>> --- a/t/helper/test-example-tap.c\n>> +++ b/t/helper/test-example-tap.c\n>> @@ -72,6 +72,8 @@ static void t_empty(void)\n>>\n>>  int cmd__example_tap(int argc, const char **argv)\n>>  {\n>> +       check(1);\n>\n> Let's include a comment that describes why we have this outside of the\n> TEST() macros so that people don't try to \"fix\" it, and so that people\n> realize it's not meant to be a _good_ example :)\n\nWell, the other examples have no such comments, either, but they are\ntests (using TEST or if_test), so they at least have descriptions.  So\nfair enough.\n\n>> diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c\n>> index 3c513ce59a..989dc758e6 100644\n>> --- a/t/unit-tests/test-lib.c\n>> +++ b/t/unit-tests/test-lib.c\n>> @@ -264,7 +264,12 @@ static void test_todo(void)\n>>\n>>  int test_assert(const char *location, const char *check, int ok)\n>>  {\n>> -       assert(ctx.running);\n>> +       if (!ctx.running) {\n>> +               test_msg(\"BUG: check outside of test at %s\",\n>> +                        make_relative(location));\n>\n> Below, `test_msg` emits a message like `skipping check '1 == 2' at\n> <loc>`. Should we include 'check' as part of the message here, or is\n> it not possible or not useful for some reason?\n\nIt's possible, of course.  Didn't do it because I didn't consider that\nwe might have multiple checks per line and the complementary \"test has\nno checks\" message  only mentions the line number.  The latter is\nfollowed by the test description in the next line, though.  So yeah,\ngood point.\n\n>\n>> +               ctx.failed = 1;\n>> +               return 0;\n>> +       }\n>>\n>>         if (ctx.result == RESULT_SKIP) {\n>>                 test_msg(\"skipping check '%s' at %s\", check,\n>> --\n>> 2.46.0\n"},{"id":"499770","messageId":"51ec32ed-f2f1-4993-92e5-1acfcdbb7c79@web.de","threadId":"61700","inReplyTo":"CAO_smVh_2FEbTAqJDMZAKMsfTKrB=NH36G6jNmMTaEOtRaP2Pg@mail.gmail.com","subject":"Re: [PATCH v4 3/6] unit-tests: add if_test","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2024-08-01T07:32:33Z","receivedAt":"2024-08-01T07:32:41Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 01.08.24 um 00:04 schrieb Kyle Lippincott:\n>\n> I'm still not wild about the name, and I'm still not convinced of the\n> readability improvements. I still want to read `if_test(...)` as\n> having a condition in the parenthesis. Tests \"skipping everything\"\n> seems like an internal implementation detail that there's disagreement\n> on whether to expose to the test author/reader or not; maybe we should\n> redesign if we don't migrate to clar or some other test framework. Do\n> we get significant benefits from a test function continuing to execute\n> after the \"skip everything\" flag has been set?\n\nThe idea is that the unit test framework may allow to skip individual\ntests in the future; see\nhttps://lore.kernel.org/git/c2de7f4e-e5dc-4324-8238-1d06795a2107@gmail.com/\n\nRené\n"},{"id":"499918","messageId":"CAO_smVi6hxYLYWPXautyHyYKV2eZitm3j0EFuSo7qbBAoGZvAw@mail.gmail.com","threadId":"61700","inReplyTo":"51ec32ed-f2f1-4993-92e5-1acfcdbb7c79@web.de","subject":"Re: [PATCH v4 3/6] unit-tests: add if_test","fromName":"Kyle Lippincott","fromEmail":"spectral@google.com","sentAt":"2024-08-02T00:48:50Z","receivedAt":"2024-08-02T00:49:08Z","isPatch":true,"sender":{"key":"spectral@google.com","avatar":"https://avatars.githubusercontent.com/u/6371650?v=4"},"body":"On Thu, Aug 1, 2024 at 12:32 AM René Scharfe <l.s.r@web.de> wrote:\n>\n> Am 01.08.24 um 00:04 schrieb Kyle Lippincott:\n> >\n> > I'm still not wild about the name, and I'm still not convinced of the\n> > readability improvements. I still want to read `if_test(...)` as\n> > having a condition in the parenthesis. Tests \"skipping everything\"\n> > seems like an internal implementation detail that there's disagreement\n> > on whether to expose to the test author/reader or not; maybe we should\n> > redesign if we don't migrate to clar or some other test framework. Do\n> > we get significant benefits from a test function continuing to execute\n> > after the \"skip everything\" flag has been set?\n>\n> The idea is that the unit test framework may allow to skip individual\n> tests in the future; see\n> https://lore.kernel.org/git/c2de7f4e-e5dc-4324-8238-1d06795a2107@gmail.com/\n\nI see. This functionality isn't currently available, but we may want\nit in the future. If that future ever happens, then we may be able to\navoid some cleanup if we keep the setup the way it is, where\n`test__run_start` returns whether or not the test actually started.\nThis future `--run` flag for the unit tests would be based on either\n(a) position in the file, like the integration tests, or (b) some\nsubstring of the description. Since the tests don't have a name,\nthere's no ability for `--run` to operate based on that. Ok.\n\nI still don't like the name, but I might be in the minority on this.\nYes, there's a control structure in use to make this work with the\nsyntax you desire, but unlike with `for_test`, there's many fewer edge\ncases added because of it: `break` and `continue` won't do anything\nwith `if_test`. I suppose that we would still need to be concerned\nabout the possibility of someone writing this:\n\nif (condition)\n    if_test(\"...\")\n        check_int(1, ==, 1);\nelse\n    ...\n\nI'm also still not personally sold on the value here.\nSingle-expression tests can be written with `TEST(check_int(1, ==, 1),\n\"equality\")`, and having a single large function with all\nmulti-statement tests in it is harder to follow, in my opinion, than\nthe alternative where the tests are in a separate function. I can only\nreally think of the case where the tests have inter-dependencies (ex:\ntest 2 sets some state that test 5 relies on) for wanting them all in\nthe same function, because then that dependency becomes a bit more\nobvious. However, getting that inter-test dependency to _not_ happen\nactually seems like a worthwhile goal (except for cases like strbuf,\nwhere if strbuf is inherently broken, we need to check that first and\nbail out). There's increased risk that someone will write a test like\nthis (purely hypothetical example of some state being carried from one\ntest to another; yes, you can accomplish the same terribleness with\nglobal variables, but more people have immediate negative reactions\nabout globals):\n\nint cmd_main(int argc UNUSED, const char **argv UNUSED)\n{\n    char tmp_path[PATH_MAX];\n    if_test(\"alpha\") {\n        /* perform basic functionality checks, like strbuf does */\n    }\n    if_test(\"beta\") {\n        /* or some other \"expensive\" initialization we don't want to\ndo if everything is broken */\n        sprintf(\"/tmp/tmp.%d\", rand());\n        do_something(tmp_path, ...);\n    }\n    if_test(\"gamma\") {\n        /*\n         * if beta didn't run, or if tests get reordered such that\ngamma executes before beta,\n         * things are going to go very poorly here\n        */\n        do_something_else(tmp_path, ...);\n    }\n    return tests_done();\n}\n\nThat said, I'm still not opposed enough to the idea to veto (plus I\ndon't have anywhere near that kind of power, and shouldn't :)). I just\ndon't think I'd ever use it.\n"}]}