threads / patch / 61737

patcht-strvec: use test_msg()

Subject: [PATCH] t-strvec: use test_msg()

## tl;dr

20 messages between Jul 4, 2024 and Jul 16, 2024. Diffs are folded; open one to read it.

replies: 19people: 5as markdown or json

René Scharfe· Jul 4, 2024, 18:04 UTC · lore

check_strvec_loc() checks each the strvec item by looping through them and comparing them with expected values. If a check fails then we'd like to know which item is affected. It reports that information by building a strbuf and delivering its contents using a failing assertion.

Here's an example in which there are less items in the strvec than expected; the index of the missing item is reported in the last line:

   # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
   #    left: 1
   #   right: 1
   # check "strvec index 1" failed at t/unit-tests/t-strvec.c:71

Note that it is also reported in the third line, i.e. the variable "nr" contains that index.

Stop printing the index explicitly for checks that already report it. The message for the same condition as above becomes:

   # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
   #    left: 1
   #   right: 1

For the string comparison, whose error message doesn't include the index, report it using the simpler and more appropriate test_msg() instead. Report the index using its actual name and format the line like the preceding ones. The message for an unexpected string value becomes:

   # check "!strcmp(vec->v[nr], str)" failed at t/unit-tests/t-strvec.c:24
   #    left: "foo"
   #   right: "bar"
   #      nr: 0
Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>
---
 t/unit-tests/t-strvec.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to t/unit-tests/t-strvec.c +6 −7
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
index d4615ab06d..236203af61 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/t-strvec.c
@@ -17,12 +17,12 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
 			break;

 		if (!check_uint(vec->nr, >, nr) ||
-		    !check_uint(vec->alloc, >, nr) ||
-		    !check_str(vec->v[nr], str)) {
-			struct strbuf msg = STRBUF_INIT;
-			strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
-			test_assert(loc, msg.buf, 0);
-			strbuf_release(&msg);
+		    !check_uint(vec->alloc, >, nr)) {
+			va_end(ap);
+			return;
+		}
+		if (!check_str(vec->v[nr], str)) {
+			test_msg("     nr: %"PRIuMAX, (uintmax_t)nr);
 			va_end(ap);
 			return;
 		}
--
2.45.2
Eric Sunshine· Jul 4, 2024, 18:31 UTC · re: René Scharfe · lore

Re: [PATCH] t-strvec: use test_msg()

On Thu, Jul 4, 2024 at 2:04 PM René Scharfe <l.s.r@web.de> wrote:
> check_strvec_loc() checks each the strvec item by looping through them
s/each the/each/
Show 5 quoted lines
> and comparing them with expected values.  If a check fails then we'd
> like to know which item is affected.  It reports that information by
> building a strbuf and delivering its contents using a failing assertion.
>
> Here's an example in which there are less items in the strvec than
s/less/fewer/
Show 30 quoted lines
> expected; the index of the missing item is reported in the last line:
>
>    # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
>    #    left: 1
>    #   right: 1
>    # check "strvec index 1" failed at t/unit-tests/t-strvec.c:71
>
> Note that it is also reported in the third line, i.e. the variable
> "nr" contains that index.
>
> Stop printing the index explicitly for checks that already report it.
> The message for the same condition as above becomes:
>
>    # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
>    #    left: 1
>    #   right: 1
>
> For the string comparison, whose error message doesn't include the
> index, report it using the simpler and more appropriate test_msg()
> instead.  Report the index using its actual name and format the line
> like the preceding ones.  The message for an unexpected string value
> becomes:
>
>    # check "!strcmp(vec->v[nr], str)" failed at t/unit-tests/t-strvec.c:24
>    #    left: "foo"
>    #   right: "bar"
>    #      nr: 0
>
> Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> ---
Missing sign-off.
Phillip Wood· Jul 5, 2024, 09:44 UTC · re: René Scharfe · lore

Re: [PATCH] t-strvec: use test_msg()

Hi René
Thanks for working on this, this looks good modulo Eric's suggestions
Best Wishes
Phillip
On 04/07/2024 19:04, René Scharfe wrote:
Show 64 quoted lines
> check_strvec_loc() checks each the strvec item by looping through them
> and comparing them with expected values.  If a check fails then we'd
> like to know which item is affected.  It reports that information by
> building a strbuf and delivering its contents using a failing assertion.
> 
> Here's an example in which there are less items in the strvec than
> expected; the index of the missing item is reported in the last line:
> 
>     # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
>     #    left: 1
>     #   right: 1
>     # check "strvec index 1" failed at t/unit-tests/t-strvec.c:71
> 
> Note that it is also reported in the third line, i.e. the variable
> "nr" contains that index.
> 
> Stop printing the index explicitly for checks that already report it.
> The message for the same condition as above becomes:
> 
>     # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
>     #    left: 1
>     #   right: 1
> 
> For the string comparison, whose error message doesn't include the
> index, report it using the simpler and more appropriate test_msg()
> instead.  Report the index using its actual name and format the line
> like the preceding ones.  The message for an unexpected string value
> becomes:
> 
>     # check "!strcmp(vec->v[nr], str)" failed at t/unit-tests/t-strvec.c:24
>     #    left: "foo"
>     #   right: "bar"
>     #      nr: 0
> 
> Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> ---
>   t/unit-tests/t-strvec.c | 12 ++++++------
>   1 file changed, 6 insertions(+), 6 deletions(-)
> 
> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
> index d4615ab06d..236203af61 100644
> --- a/t/unit-tests/t-strvec.c
> +++ b/t/unit-tests/t-strvec.c
> @@ -17,12 +17,12 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
>   			break;
> 
>   		if (!check_uint(vec->nr, >, nr) ||
> -		    !check_uint(vec->alloc, >, nr) ||
> -		    !check_str(vec->v[nr], str)) {
> -			struct strbuf msg = STRBUF_INIT;
> -			strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
> -			test_assert(loc, msg.buf, 0);
> -			strbuf_release(&msg);
> +		    !check_uint(vec->alloc, >, nr)) {
> +			va_end(ap);
> +			return;
> +		}
> +		if (!check_str(vec->v[nr], str)) {
> +			test_msg("     nr: %"PRIuMAX, (uintmax_t)nr);
>   			va_end(ap);
>   			return;
>   		}
> --
> 2.45.2
René Scharfe· Jul 5, 2024, 17:03 UTC · re: René Scharfe · lore

[PATCH v2] t-strvec: use test_msg()

check_strvec_loc() checks each strvec item by looping through them and comparing them with expected values. If a check fails then we'd like to know which item is affected. It reports that information by building a strbuf and delivering its contents using a failing assertion, e.g. if there are fewer items in the strvec than expected:

   # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
   #    left: 1
   #   right: 1
   # check "strvec index 1" failed at t/unit-tests/t-strvec.c:71

Note that the index variable is "nr" and thus the interesting value is reported twice in that example (in lines three and four).

Stop printing the index explicitly for checks that already report it. The message for the same condition as above becomes:

   # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
   #    left: 1
   #   right: 1

For the string comparison, whose error message doesn't include the index, report it using the simpler and more appropriate test_msg() instead. Report the index using its actual variable name and format the line like the preceding ones. The message for an unexpected string value becomes:

   # check "!strcmp(vec->v[nr], str)" failed at t/unit-tests/t-strvec.c:24
   #    left: "foo"
   #   right: "bar"
   #      nr: 0
Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
Changes since v1:
- Typo fix.
- Grammar fix.
- Reworded problem description for brevity.
- Qualify "name" in the last paragraph for clarity.
- Add sign-off.
- No code changes.
Thank you, Eric!
 t/unit-tests/t-strvec.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to t/unit-tests/t-strvec.c +6 −7
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
index d4615ab06d..236203af61 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/t-strvec.c
@@ -17,12 +17,12 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
 			break;

 		if (!check_uint(vec->nr, >, nr) ||
-		    !check_uint(vec->alloc, >, nr) ||
-		    !check_str(vec->v[nr], str)) {
-			struct strbuf msg = STRBUF_INIT;
-			strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
-			test_assert(loc, msg.buf, 0);
-			strbuf_release(&msg);
+		    !check_uint(vec->alloc, >, nr)) {
+			va_end(ap);
+			return;
+		}
+		if (!check_str(vec->v[nr], str)) {
+			test_msg("     nr: %"PRIuMAX, (uintmax_t)nr);
 			va_end(ap);
 			return;
 		}
--
2.45.2
Jeff King· Jul 9, 2024, 11:32 UTC · re: René Scharfe · lore

Re: [PATCH v2] t-strvec: use test_msg()

On Fri, Jul 05, 2024 at 07:03:36PM +0200, René Scharfe wrote:
Show 23 quoted lines
> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
> index d4615ab06d..236203af61 100644
> --- a/t/unit-tests/t-strvec.c
> +++ b/t/unit-tests/t-strvec.c
> @@ -17,12 +17,12 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
>  			break;
> 
>  		if (!check_uint(vec->nr, >, nr) ||
> -		    !check_uint(vec->alloc, >, nr) ||
> -		    !check_str(vec->v[nr], str)) {
> -			struct strbuf msg = STRBUF_INIT;
> -			strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
> -			test_assert(loc, msg.buf, 0);
> -			strbuf_release(&msg);
> +		    !check_uint(vec->alloc, >, nr)) {
> +			va_end(ap);
> +			return;
> +		}
> +		if (!check_str(vec->v[nr], str)) {
> +			test_msg("     nr: %"PRIuMAX, (uintmax_t)nr);
>  			va_end(ap);
>  			return;
>  		}

The "loc" parameter to the function is now unused. Should it be removed, or is it a bug that we are no longer reporting the caller's location? Should we be using check_str_loc() in the post-image?

-Peff
René Scharfe· Jul 14, 2024, 10:17 UTC · re: Jeff King · lore

Re: [PATCH v2] t-strvec: use test_msg()

Am 09.07.24 um 13:32 schrieb Jeff King:
Show 28 quoted lines
> On Fri, Jul 05, 2024 at 07:03:36PM +0200, René Scharfe wrote:
>
>> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
>> index d4615ab06d..236203af61 100644
>> --- a/t/unit-tests/t-strvec.c
>> +++ b/t/unit-tests/t-strvec.c
>> @@ -17,12 +17,12 @@ static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
>>  			break;
>>
>>  		if (!check_uint(vec->nr, >, nr) ||
>> -		    !check_uint(vec->alloc, >, nr) ||
>> -		    !check_str(vec->v[nr], str)) {
>> -			struct strbuf msg = STRBUF_INIT;
>> -			strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
>> -			test_assert(loc, msg.buf, 0);
>> -			strbuf_release(&msg);
>> +		    !check_uint(vec->alloc, >, nr)) {
>> +			va_end(ap);
>> +			return;
>> +		}
>> +		if (!check_str(vec->v[nr], str)) {
>> +			test_msg("     nr: %"PRIuMAX, (uintmax_t)nr);
>>  			va_end(ap);
>>  			return;
>>  		}
>
> The "loc" parameter to the function is now unused. Should it be removed,
> or is it a bug that we are no longer reporting the caller's location?

It's a bug. If only there was a way to detect such an unused parameter automatically.. ;->

> Should we be using check_str_loc() in the post-image?

Yes, and check_uint_loc() and check_pointer_eq_loc() as well. Which would be a pain. Or we drag everything into the macro check_strvec and get the caller's line number for free.

René
Jeff King· Jul 16, 2024, 01:43 UTC · re: René Scharfe · lore

Re: [PATCH v2] t-strvec: use test_msg()

On Sun, Jul 14, 2024 at 12:17:09PM +0200, René Scharfe wrote:
Show 5 quoted lines
> > Should we be using check_str_loc() in the post-image?
> 
> Yes, and check_uint_loc() and check_pointer_eq_loc() as well.  Which
> would be a pain.  Or we drag everything into the macro check_strvec and
> get the caller's line number for free.

Is it that big of a pain? It's mostly just passing "loc" along to the relative functions. To me it is more a problem that it is super easy to forget.

Are the unit tests themselves multi-threaded within a single program? I'd think not. In which case, I kind of wonder if a simpler pattern would be to just set a global "location" variable (probably as a stack of strings) that all of the individual check functions used. And then we could set it once at the top-level of the test which wants its location reported, and any helper functions that get called in the middle would not have to care. And existing check_foo() functions could use the current file/line location if the stack is empty (so code that isn't using helper functions can remain unaffected).

-Peff
René Scharfe· Jul 16, 2024, 16:43 UTC · re: Jeff King · lore

Re: [PATCH v2] t-strvec: use test_msg()

Am 16.07.24 um 03:43 schrieb Jeff King:
Show 10 quoted lines
> On Sun, Jul 14, 2024 at 12:17:09PM +0200, René Scharfe wrote:
>
>>> Should we be using check_str_loc() in the post-image?
>>
>> Yes, and check_uint_loc() and check_pointer_eq_loc() as well.  Which
>> would be a pain.  Or we drag everything into the macro check_strvec and
>> get the caller's line number for free.
>
> Is it that big of a pain? It's mostly just passing "loc" along to the
> relative functions.

That part is bearable. The pain comes from the need to pass in arguments multiple times, for the stringified check description, as part of the check result and as separate values.

It could be mitigated by adding a new macro that takes loc, a, op, and b, perhaps called CHECK_UINT_LOC. That additional evaluation step doesn't work nicely with arguments that are themselves macros, though, like NULL for string or pointer checks, as those will be expanded, changing the message.

> Are the unit tests themselves multi-threaded within a single program?

No, and check_pointer_eq, check_int, check_uint, and check_char use shared global variables to store temporary values (comments rightly warn that "this is not thread safe").

Show 8 quoted lines
> I'd think not. In which case, I kind of wonder if a simpler pattern
> would be to just set a global "location" variable (probably as a stack
> of strings) that all of the individual check functions used. And then we
> could set it once at the top-level of the test which wants its location
> reported, and any helper functions that get called in the middle would
> not have to care. And existing check_foo() functions could use the
> current file/line location if the stack is empty (so code that isn't
> using helper functions can remain unaffected).

That doesn't sound simpler to me, quite the opposite actually. To the point that I don't dare to ask for a demo. o_O

Or perhaps I need to revisit it when I'm no longer tired and hungry.
René
René Scharfe· Jul 14, 2024, 17:06 UTC · re: René Scharfe · lore

[PATCH v2 2/1] t-strvec: improve check_strvec() output

The macro check_strvec calls the function check_strvec_loc(), which performs the actual checks. They report the line number inside that function on error, which is not very helpful. Before the previous patch half of them triggered an assertion that reported the caller's line number using a custom message, which was more useful, but a bit awkward.

Improve the output by getting rid of check_strvec_loc() and performing all checks within check_strvec, as they then report the line number of the call site, aiding in finding the broken test. Determine the number of items and check it up front to avoid having to do them both in the loop and at the end.

Sanity check the expected items to make sure there are any and that the last one is NULL, as the compiler no longer does that for us with the removal of the function attribute LAST_ARG_MUST_BE_NULL.

Use only the actual strvec name passed to the macro, the internal "expect" array name and an index "i" in the output, for clarity. While "expect" does not exist at the call site, it's reasonably easy to infer that it's referring to the NULL-terminated list of expected strings, converted to an array.

Here's the output with less items than expected in the strvec before:
 # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
 #    left: 1
 #   right: 1
... and with the patch:
 # check "(&vec)->nr == ARRAY_SIZE(expect) - 1" failed at t/unit-tests/t-strvec.c:53
 #    left: 1
 #   right: 2
With too many items in the strvec we got before:
 # check "vec->nr == nr" failed at t/unit-tests/t-strvec.c:34
 #    left: 1
 #   right: 0
 # check "vec->v[nr] == NULL" failed at t/unit-tests/t-strvec.c:36
 #    left: 0x6000004b8010
 #   right: 0x0
... and with the patch:
 # check "(&vec)->nr == ARRAY_SIZE(expect) - 1" failed at t/unit-tests/t-strvec.c:53
 #    left: 1
 #   right: 0
A broken alloc value was reported like this:
 # check "vec->alloc > nr" failed at t/unit-tests/t-strvec.c:20
 #    left: 0
 #   right: 0
 ... and with the patch:
 # check "(&vec)->nr <= (&vec)->alloc" failed at t/unit-tests/t-strvec.c:56
 #    left: 2
 #   right: 0
An unexpected string value was reported like this:
 # check "!strcmp(vec->v[nr], str)" failed at t/unit-tests/t-strvec.c:24
 #    left: "foo"
 #   right: "bar"
 #      nr: 0
... and with the patch:
 # check "!strcmp((&vec)->v[i], expect[i])" failed at t/unit-tests/t-strvec.c:53
 #    left: "foo"
 #   right: "bar"
 #       i: 0
If the strvec is not NULL terminated, we got:
 # check "vec->v[nr] == NULL" failed at t/unit-tests/t-strvec.c:36
 #    left: 0x102c3abc8
 #   right: 0x0
... and with the patch we get the line number of the caller:
 # check "!strcmp((&vec)->v[i], expect[i])" failed at t/unit-tests/t-strvec.c:53
 #    left: "bar"
 #   right: NULL
 #       i: 1

check_strvec calls without a trailing NULL were detected at compile time before:

 t/unit-tests/t-strvec.c:71:2: error: missing sentinel in function call [-Werror,-Wsentinel]
... and with the patch it's only found at runtime:
 # check "expect[ARRAY_SIZE(expect) - 1] == NULL" failed at t/unit-tests/t-strvec.c:53
 #    left: 0x100e5a663
 #   right: 0x0

We can let check_strvec add the terminating NULL for us and remove it from callers, making it impossible to forget. Leave that conversion for a future patch, though, since this reimplementation is already intrusive enough.

Reported-by: Jeff King <peff@peff.net>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
For next, on top of c6f35e529e t-strvec: use test_msg().
 t/unit-tests/t-strvec.c | 46 +++++++++++++----------------------------
 1 file changed, 14 insertions(+), 32 deletions(-)
Show changes to t/unit-tests/t-strvec.c +14 −33
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
index 236203af61..fdb28ba220 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/t-strvec.c
@@ -3,38 +3,20 @@
 #include "strvec.h"

 #define check_strvec(vec, ...) \
-	check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__)
-LAST_ARG_MUST_BE_NULL
-static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
-{
-	va_list ap;
-	size_t nr = 0;
-
-	va_start(ap, vec);
-	while (1) {
-		const char *str = va_arg(ap, const char *);
-		if (!str)
-			break;
-
-		if (!check_uint(vec->nr, >, nr) ||
-		    !check_uint(vec->alloc, >, nr)) {
-			va_end(ap);
-			return;
-		}
-		if (!check_str(vec->v[nr], str)) {
-			test_msg("     nr: %"PRIuMAX, (uintmax_t)nr);
-			va_end(ap);
-			return;
-		}
-
-		nr++;
-	}
-	va_end(ap);
-
-	check_uint(vec->nr, ==, nr);
-	check_uint(vec->alloc, >=, nr);
-	check_pointer_eq(vec->v[nr], NULL);
-}
+	do { \
+		const char *expect[] = { __VA_ARGS__ }; \
+		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
+		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
+		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
+		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
+			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
+				if (!check_str((vec)->v[i], expect[i])) { \
+					test_msg("      i: %"PRIuMAX, i); \
+					break; \
+				} \
+			} \
+		} \
+	} while (0)

 static void t_static_init(void)
 {
--
2.45.2
Junio C Hamano· Jul 15, 2024, 14:44 UTC · re: René Scharfe · lore

Re: [PATCH v2 2/1] t-strvec: improve check_strvec() output

René Scharfe <l.s.r@web.de> writes:
Show 16 quoted lines
> The macro check_strvec calls the function check_strvec_loc(), which
> performs the actual checks.  They report the line number inside that
> function on error, which is not very helpful.  Before the previous
> patch half of them triggered an assertion that reported the caller's
> line number using a custom message, which was more useful, but a bit
> awkward.
> ...
> We can let check_strvec add the terminating NULL for us and remove it
> from callers, making it impossible to forget.  Leave that conversion for
> a future patch, though, since this reimplementation is already intrusive
> enough.
>
> Reported-by: Jeff King <peff@peff.net>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
> For next, on top of c6f35e529e t-strvec: use test_msg().
Thanks.
René Scharfe· Jul 14, 2024, 17:06 UTC · re: René Scharfe · lore

[PATCH v2 3/1] t-strvec: tighten .alloc check in check_strvec

The members .nr and .alloc of an empty strvec are both zero and the trailing NULL is provided by the shared global variable empty_strvec. Once at least one item is added, .alloc needs to be bigger than .nr to leave room for the terminating NULL. The current .alloc check only tests whether it's bigger or equal to .nr in all cases.

Tighten it depending on whether the strvec is backed by empty_strvec or not. A report for a non-zero .alloc in an empty strvec looks like this:

 # check "(&vec)->nr == (&vec)->alloc" failed at t/unit-tests/t-strvec.c:55
 #    left: 0
 #   right: 1
And .alloc being .equal to .nr in a non-empty strvec results in:
 # check "(&vec)->nr < (&vec)->alloc" failed at t/unit-tests/t-strvec.c:55
 #    left: 1
 #   right: 1
Signed-off-by: René Scharfe <l.s.r@web.de>
---
Bonus patch.
 t/unit-tests/t-strvec.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Show changes to t/unit-tests/t-strvec.c +3 −2
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
index fdb28ba220..6a4d425840 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/t-strvec.c
@@ -8,7 +8,9 @@
 		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
 		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
 		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
-		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
+		    ((vec)->v == empty_strvec ? \
+		     check_uint((vec)->nr, ==, (vec)->alloc) : \
+		     check_uint((vec)->nr, <, (vec)->alloc))) { \
 			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
 				if (!check_str((vec)->v[i], expect[i])) { \
 					test_msg("      i: %"PRIuMAX, i); \
--
2.45.2
Junio C Hamano· Jul 15, 2024, 14:43 UTC · re: René Scharfe · lore

Re: [PATCH v2 3/1] t-strvec: tighten .alloc check in check_strvec

René Scharfe <l.s.r@web.de> writes:
Show 12 quoted lines
> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
> index fdb28ba220..6a4d425840 100644
> --- a/t/unit-tests/t-strvec.c
> +++ b/t/unit-tests/t-strvec.c
> @@ -8,7 +8,9 @@
>  		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
>  		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
>  		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
> -		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
> +		    ((vec)->v == empty_strvec ? \
> +		     check_uint((vec)->nr, ==, (vec)->alloc) : \
> +		     check_uint((vec)->nr, <, (vec)->alloc))) { \

Not a huge deal but with empty_strvec, don't we want to barf if nr==alloc==1?

Show 5 quoted lines
>  			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
>  				if (!check_str((vec)->v[i], expect[i])) { \
>  					test_msg("      i: %"PRIuMAX, i); \
> --
> 2.45.2
René Scharfe· Jul 15, 2024, 16:47 UTC · re: Junio C Hamano · lore

Re: [PATCH v2 3/1] t-strvec: tighten .alloc check in check_strvec

Am 15.07.24 um 16:43 schrieb Junio C Hamano:
Show 17 quoted lines
> René Scharfe <l.s.r@web.de> writes:
>
>> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
>> index fdb28ba220..6a4d425840 100644
>> --- a/t/unit-tests/t-strvec.c
>> +++ b/t/unit-tests/t-strvec.c
>> @@ -8,7 +8,9 @@
>>  		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
>>  		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
>>  		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
>> -		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
>> +		    ((vec)->v == empty_strvec ? \
>> +		     check_uint((vec)->nr, ==, (vec)->alloc) : \
>> +		     check_uint((vec)->nr, <, (vec)->alloc))) { \
>
> Not a huge deal but with empty_strvec, don't we want to barf if
> nr==alloc==1?

Yes, and that's handled by the comparison with ARRAY_SIZE(expect) - 1 above. Perhaps comparing to 0 explicitly would be clearer. My thinking was just to split up the old <= check into its < and == cases, without changing anything else..

Show 6 quoted lines
>
>>  			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
>>  				if (!check_str((vec)->v[i], expect[i])) { \
>>  					test_msg("      i: %"PRIuMAX, i); \
>> --
>> 2.45.2
Junio C Hamano· Jul 15, 2024, 17:27 UTC · re: René Scharfe · lore

Re: [PATCH v2 3/1] t-strvec: tighten .alloc check in check_strvec

René Scharfe <l.s.r@web.de> writes:
Show 11 quoted lines
>>>  		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
>>> -		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
>>> +		    ((vec)->v == empty_strvec ? \
>>> +		     check_uint((vec)->nr, ==, (vec)->alloc) : \
>>> +		     check_uint((vec)->nr, <, (vec)->alloc))) { \
>>
>> Not a huge deal but with empty_strvec, don't we want to barf if
>> nr==alloc==1?
>
> Yes, and that's handled by the comparison with ARRAY_SIZE(expect) - 1
> above.
Ah, OK, thanks.
René Scharfe· Jul 14, 2024, 10:17 UTC · re: René Scharfe · lore

[PATCH v3] t-strvec: improve check_strvec() output

The macro check_strvec calls the function check_strvec_loc(), which performs the actual checks. They report the line number inside that function on error, which is not very helpful. Half of them trigger an assertion with a custom message that reports the line of the check_strvec call, which is more useful, but a bit awkward.

Improve the output by getting rid of check_strvec_loc() and performing all checks within check_strvec, as they then report the line number of the call site, aiding in finding the broken test. Determine the number of items and check it up front to avoid having to do them both in the loop and at the end.

Sanity check the expected items to make sure there are any and that the last one is NULL, as the compiler no longer does that for us with the removal of the function attribute LAST_ARG_MUST_BE_NULL.

Use only the actual strvec name passed to the macro, the internal "expect" array name and an index "i" in the output, for clarity. While "expect" does not exist at the call site, it's reasonably easy to infer that its referring to the NULL-terminated list of expected strings, converted to an array. Align the "i" with previous lines in the output.

Here's the output with less items than expected in the strvec before:
 # check "vec->nr > nr" failed at t/unit-tests/t-strvec.c:19
 #    left: 1
 #   right: 1
 # check "strvec index 1" failed at t/unit-tests/t-strvec.c:71
... and with the patch we get the more concise and still comprehensive:
 # check "(&vec)->nr == ARRAY_SIZE(expect) - 1" failed at t/unit-tests/t-strvec.c:53
 #    left: 1
 #   right: 2
With too many items in the strvec we got before:
 # check "vec->nr == nr" failed at t/unit-tests/t-strvec.c:34
 #    left: 1
 #   right: 0
 # check "vec->v[nr] == NULL" failed at t/unit-tests/t-strvec.c:36
 #    left: 0x6000019a40b0
 #   right: 0x0

... and with the patch we get the more concise and informative output that includes the caller's line number:

 # check "(&vec)->nr == ARRAY_SIZE(expect) - 1" failed at t/unit-tests/t-strvec.c:53
 #    left: 1
 #   right: 0
A broken alloc value was reported like this:
 # check "vec->alloc > nr" failed at t/unit-tests/t-strvec.c:20
 #    left: 0
 #   right: 0
 # check "strvec index 0" failed at t/unit-tests/t-strvec.c:74
 ... and with the patch:
 # check "(&vec)->nr <= (&vec)->alloc" failed at t/unit-tests/t-strvec.c:56
 #    left: 2
 #   right: 0

Note that .alloc == .nr is only valid if the strvec is empty, but check_strvec doesn't detect this error with or without this patch. Leave that for later.

An unexpected string value was reported like this:
 # check "!strcmp(vec->v[nr], str)" failed at t/unit-tests/t-strvec.c:21
 #    left: "foo"
 #   right: "bar"
 # check "strvec index 0" failed at t/unit-tests/t-strvec.c:71
... and with the patch we get:
 # check "!strcmp((&vec)->v[i], expect[i])" failed at t/unit-tests/t-strvec.c:53
 #    left: "foo"
 #   right: "bar"
 #       i: 0
If the strvec is not NULL terminated, we got:
 # check "vec->v[nr] == NULL" failed at t/unit-tests/t-strvec.c:36
 #    left: 0x10444e9e4
 #   right: 0x0
... and with the patch we get the line number of the caller:
 # check "!strcmp((&vec)->v[i], expect[i])" failed at t/unit-tests/t-strvec.c:53
 #    left: "bar"
 #   right: NULL
 #       i: 1

check_strvec calls without a trailing NULL were detected at compile time before:

 t/unit-tests/t-strvec.c:71:2: error: missing sentinel in function call [-Werror,-Wsentinel]
... and with the patch it's only found at runtime:
 # check "expect[ARRAY_SIZE(expect) - 1] == NULL" failed at t/unit-tests/t-strvec.c:53
 #    left: 0x100e5a663
 #   right: 0x0

We can let check_strvec add the terminating NULL for us and remove it from callers, making it impossible to forget. Leave that conversion for a future patch, though, since this reimplementation is already intrusive enough.

Reported-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
Changes since v2:
- Replace check_strvec_loc() with macro code.
- Rewrite commit message and subject accordingly.
 t/unit-tests/t-strvec.c | 46 +++++++++++++----------------------------
 1 file changed, 14 insertions(+), 32 deletions(-)
Show changes to t/unit-tests/t-strvec.c +14 −33
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
index d4615ab06d..fdb28ba220 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/t-strvec.c
@@ -3,38 +3,20 @@
 #include "strvec.h"

 #define check_strvec(vec, ...) \
-	check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__)
-LAST_ARG_MUST_BE_NULL
-static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
-{
-	va_list ap;
-	size_t nr = 0;
-
-	va_start(ap, vec);
-	while (1) {
-		const char *str = va_arg(ap, const char *);
-		if (!str)
-			break;
-
-		if (!check_uint(vec->nr, >, nr) ||
-		    !check_uint(vec->alloc, >, nr) ||
-		    !check_str(vec->v[nr], str)) {
-			struct strbuf msg = STRBUF_INIT;
-			strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
-			test_assert(loc, msg.buf, 0);
-			strbuf_release(&msg);
-			va_end(ap);
-			return;
-		}
-
-		nr++;
-	}
-	va_end(ap);
-
-	check_uint(vec->nr, ==, nr);
-	check_uint(vec->alloc, >=, nr);
-	check_pointer_eq(vec->v[nr], NULL);
-}
+	do { \
+		const char *expect[] = { __VA_ARGS__ }; \
+		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
+		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
+		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
+		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
+			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
+				if (!check_str((vec)->v[i], expect[i])) { \
+					test_msg("      i: %"PRIuMAX, i); \
+					break; \
+				} \
+			} \
+		} \
+	} while (0)

 static void t_static_init(void)
 {
--
2.45.2
Jeff King· Jul 16, 2024, 04:37 UTC · re: René Scharfe · lore

Re: [PATCH v3] t-strvec: improve check_strvec() output

On Sun, Jul 14, 2024 at 12:17:53PM +0200, René Scharfe wrote:
Show 14 quoted lines
> +	do { \
> +		const char *expect[] = { __VA_ARGS__ }; \
> +		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
> +		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
> +		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
> +		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
> +			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
> +				if (!check_str((vec)->v[i], expect[i])) { \
> +					test_msg("      i: %"PRIuMAX, i); \
> +					break; \
> +				} \
> +			} \
> +		} \
> +	} while (0)

The linux32 CI job seems to complain about this, since the concrete type of "i" (a size_t) is "unsigned int" there, but PRIuMAX is %llu. Presumably you just need to cast to uintmax_t.

-Peff
Junio C Hamano· Jul 16, 2024, 15:20 UTC · re: Jeff King · lore

Re: [PATCH v3] t-strvec: improve check_strvec() output

Jeff King <peff@peff.net> writes:
Show 20 quoted lines
> On Sun, Jul 14, 2024 at 12:17:53PM +0200, René Scharfe wrote:
>
>> +	do { \
>> +		const char *expect[] = { __VA_ARGS__ }; \
>> +		if (check_uint(ARRAY_SIZE(expect), >, 0) && \
>> +		    check_pointer_eq(expect[ARRAY_SIZE(expect) - 1], NULL) && \
>> +		    check_uint((vec)->nr, ==, ARRAY_SIZE(expect) - 1) && \
>> +		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
>> +			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
>> +				if (!check_str((vec)->v[i], expect[i])) { \
>> +					test_msg("      i: %"PRIuMAX, i); \
>> +					break; \
>> +				} \
>> +			} \
>> +		} \
>> +	} while (0)
>
> The linux32 CI job seems to complain about this, since the concrete type
> of "i" (a size_t) is "unsigned int" there, but PRIuMAX is %llu.
> Presumably you just need to cast to uintmax_t.

Yeah, I noticed it, too, but unfortunately it was after pushing it out and seeing it break there X-<.

René Scharfe· Jul 16, 2024, 16:14 UTC · re: Junio C Hamano · lore

[PATCH] t-strvec: fix type mismatch in check_strvec

Cast i from size_t to uintmax_t to match the format string.
Reported-by: Jeff King <peff@peff.net>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
Oops, sorry about that. :-(
PRIuMAX is "lu" on my system, so the compiler didn't flag the mistake.
 t/unit-tests/t-strvec.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Show changes to t/unit-tests/t-strvec.c +2 −2
diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
index fdb28ba220..fa1a041469 100644
--- a/t/unit-tests/t-strvec.c
+++ b/t/unit-tests/t-strvec.c
@@ -11,7 +11,8 @@
 		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
 			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
 				if (!check_str((vec)->v[i], expect[i])) { \
-					test_msg("      i: %"PRIuMAX, i); \
+					test_msg("      i: %"PRIuMAX, \
+						 (uintmax_t)i); \
 					break; \
 				} \
 			} \
--
2.45.2
Junio C Hamano· Jul 16, 2024, 16:33 UTC · re: René Scharfe · lore

Re: [PATCH] t-strvec: fix type mismatch in check_strvec

René Scharfe <l.s.r@web.de> writes:
Show 11 quoted lines
> Cast i from size_t to uintmax_t to match the format string.
>
> Reported-by: Jeff King <peff@peff.net>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
> Oops, sorry about that. :-(
>
> PRIuMAX is "lu" on my system, so the compiler didn't flag the mistake.
>
>  t/unit-tests/t-strvec.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)

The fix is of course identical to what I added to the integration I prepared, but the line-wrapping is better in this version, so I'll replace ;-)

Show 16 quoted lines
> diff --git a/t/unit-tests/t-strvec.c b/t/unit-tests/t-strvec.c
> index fdb28ba220..fa1a041469 100644
> --- a/t/unit-tests/t-strvec.c
> +++ b/t/unit-tests/t-strvec.c
> @@ -11,7 +11,8 @@
>  		    check_uint((vec)->nr, <=, (vec)->alloc)) { \
>  			for (size_t i = 0; i < ARRAY_SIZE(expect); i++) { \
>  				if (!check_str((vec)->v[i], expect[i])) { \
> -					test_msg("      i: %"PRIuMAX, i); \
> +					test_msg("      i: %"PRIuMAX, \
> +						 (uintmax_t)i); \
>  					break; \
>  				} \
>  			} \
> --
> 2.45.2

← back to recent threads