diff mbox series

[6/6] t-strbuf: use TEST_RUN

Message ID 1bf053ae-957e-4e9a-90f0-11cc76848ce9@web.de (mailing list archive)
State New
Headers show
Series unit-tests: add and use TEST_RUN to simplify tests | expand

Commit Message

René Scharfe June 29, 2024, 3:47 p.m. UTC
The macro TEST takes a single expression.  If a test requires multiple
statements then they need to be placed in a function that's called in
the TEST expression.  The functions setup() and setup_populated() here
are used for that purpose and take another function as an argument,
making the control flow hard to follow.

Remove the overhead of these functions by using TEST_RUN instead.  Move
their duplicate post-condition checks into a new helper, t_release(),
and let t_addch() and t_addstr() accept properly typed input parameters
instead of void pointers.

Use the fully checking t_addstr() for adding initial values instead of
only doing only a length comparison -- there's no need for skipping the
other checks.

This results in test cases that look much more like strbuf usage in
production code, only with checked strbuf functions replaced by checking
wrappers.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 t/unit-tests/t-strbuf.c | 79 +++++++++++++++++++++--------------------
 1 file changed, 41 insertions(+), 38 deletions(-)

--
2.45.2

Comments

Josh Steadmon July 1, 2024, 7:58 p.m. UTC | #1
On 2024.06.29 17:47, René Scharfe wrote:
> The macro TEST takes a single expression.  If a test requires multiple
> statements then they need to be placed in a function that's called in
> the TEST expression.  The functions setup() and setup_populated() here
> are used for that purpose and take another function as an argument,
> making the control flow hard to follow.
> 
> Remove the overhead of these functions by using TEST_RUN instead.  Move
> their duplicate post-condition checks into a new helper, t_release(),
> and let t_addch() and t_addstr() accept properly typed input parameters
> instead of void pointers.
> 
> Use the fully checking t_addstr() for adding initial values instead of
> only doing only a length comparison -- there's no need for skipping the
> other checks.
> 
> This results in test cases that look much more like strbuf usage in
> production code, only with checked strbuf functions replaced by checking
> wrappers.
> 
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
>  t/unit-tests/t-strbuf.c | 79 +++++++++++++++++++++--------------------
>  1 file changed, 41 insertions(+), 38 deletions(-)
> 
> diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
> index 6027dafef7..c8e39ddda7 100644
> --- a/t/unit-tests/t-strbuf.c
> +++ b/t/unit-tests/t-strbuf.c
> @@ -1,32 +1,6 @@
>  #include "test-lib.h"
>  #include "strbuf.h"
> 
> -/* wrapper that supplies tests with an empty, initialized strbuf */
> -static void setup(void (*f)(struct strbuf*, const void*),
> -		  const void *data)
> -{
> -	struct strbuf buf = STRBUF_INIT;
> -
> -	f(&buf, data);
> -	strbuf_release(&buf);
> -	check_uint(buf.len, ==, 0);
> -	check_uint(buf.alloc, ==, 0);
> -}
> -
> -/* wrapper that supplies tests with a populated, initialized strbuf */
> -static void setup_populated(void (*f)(struct strbuf*, const void*),
> -			    const char *init_str, const void *data)
> -{
> -	struct strbuf buf = STRBUF_INIT;
> -
> -	strbuf_addstr(&buf, init_str);
> -	check_uint(buf.len, ==, strlen(init_str));
> -	f(&buf, data);
> -	strbuf_release(&buf);
> -	check_uint(buf.len, ==, 0);
> -	check_uint(buf.alloc, ==, 0);
> -}
> -
>  static int assert_sane_strbuf(struct strbuf *buf)
>  {
>  	/* Initialized strbufs should always have a non-NULL buffer */
> @@ -66,10 +40,8 @@ static void t_dynamic_init(void)
>  	strbuf_release(&buf);
>  }
> 
> -static void t_addch(struct strbuf *buf, const void *data)
> +static void t_addch(struct strbuf *buf, int ch)
>  {
> -	const char *p_ch = data;
> -	const char ch = *p_ch;
>  	size_t orig_alloc = buf->alloc;
>  	size_t orig_len = buf->len;
> 
> @@ -85,9 +57,8 @@ static void t_addch(struct strbuf *buf, const void *data)
>  	check_char(buf->buf[buf->len], ==, '\0');
>  }
> 
> -static void t_addstr(struct strbuf *buf, const void *data)
> +static void t_addstr(struct strbuf *buf, const char *text)
>  {
> -	const char *text = data;
>  	size_t len = strlen(text);
>  	size_t orig_alloc = buf->alloc;
>  	size_t orig_len = buf->len;
> @@ -105,18 +76,50 @@ static void t_addstr(struct strbuf *buf, const void *data)
>  	check_str(buf->buf + orig_len, text);
>  }
> 
> +static void t_release(struct strbuf *sb)
> +{
> +	strbuf_release(sb);
> +	check_uint(sb->len, ==, 0);
> +	check_uint(sb->alloc, ==, 0);
> +}
> +
>  int cmd_main(int argc, const char **argv)
>  {
>  	if (!TEST(t_static_init(), "static initialization works"))
>  		test_skip_all("STRBUF_INIT is broken");
>  	TEST(t_dynamic_init(), "dynamic initialization works");

IIUC you're leaving t_static_init() as-is so that we can determine
whether or not to skip the rest of the tests, but is there a reason you
didn't convert t_dynamic_init() here?

> -	TEST(setup(t_addch, "a"), "strbuf_addch adds char");
> -	TEST(setup(t_addch, ""), "strbuf_addch adds NUL char");
> -	TEST(setup_populated(t_addch, "initial value", "a"),
> -	     "strbuf_addch appends to initial value");
> -	TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string");
> -	TEST(setup_populated(t_addstr, "initial value", "hello there"),
> -	     "strbuf_addstr appends string to initial value");
> +
> +	if (TEST_RUN("strbuf_addch adds char")) {
> +		struct strbuf sb = STRBUF_INIT;
> +		t_addch(&sb, 'a');
> +		t_release(&sb);
> +	}
> +
> +	if (TEST_RUN("strbuf_addch adds NUL char")) {
> +		struct strbuf sb = STRBUF_INIT;
> +		t_addch(&sb, '\0');
> +		t_release(&sb);
> +	}
> +
> +	if (TEST_RUN("strbuf_addch appends to initial value")) {
> +		struct strbuf sb = STRBUF_INIT;
> +		t_addstr(&sb, "initial value");
> +		t_addch(&sb, 'a');
> +		t_release(&sb);
> +	}
> +
> +	if (TEST_RUN("strbuf_addstr adds string")) {
> +		struct strbuf sb = STRBUF_INIT;
> +		t_addstr(&sb, "hello there");
> +		t_release(&sb);
> +	}
> +
> +	if (TEST_RUN("strbuf_addstr appends string to initial value")) {
> +		struct strbuf sb = STRBUF_INIT;
> +		t_addstr(&sb, "initial value");
> +		t_addstr(&sb, "hello there");
> +		t_release(&sb);
> +	}
> 
>  	return test_done();
>  }
> --
> 2.45.2

I think this commit in particular shows how TEST_RUN() is more
convenient than TEST(). (Although, arguably we shouldn't have allowed
the setup() + callback situation to start with.)
René Scharfe July 1, 2024, 8:18 p.m. UTC | #2
Am 01.07.24 um 21:58 schrieb Josh Steadmon:
> On 2024.06.29 17:47, René Scharfe wrote:
>> The macro TEST takes a single expression.  If a test requires multiple
>> statements then they need to be placed in a function that's called in
>> the TEST expression.  The functions setup() and setup_populated() here
>> are used for that purpose and take another function as an argument,
>> making the control flow hard to follow.
>>
>> Remove the overhead of these functions by using TEST_RUN instead.  Move
>> their duplicate post-condition checks into a new helper, t_release(),
>> and let t_addch() and t_addstr() accept properly typed input parameters
>> instead of void pointers.
>>
>> Use the fully checking t_addstr() for adding initial values instead of
>> only doing only a length comparison -- there's no need for skipping the
>> other checks.
>>
>> This results in test cases that look much more like strbuf usage in
>> production code, only with checked strbuf functions replaced by checking
>> wrappers.
>>
>> Signed-off-by: René Scharfe <l.s.r@web.de>
>> ---
>>  t/unit-tests/t-strbuf.c | 79 +++++++++++++++++++++--------------------
>>  1 file changed, 41 insertions(+), 38 deletions(-)
>>
>> diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
>> index 6027dafef7..c8e39ddda7 100644
>> --- a/t/unit-tests/t-strbuf.c
>> +++ b/t/unit-tests/t-strbuf.c
>> @@ -1,32 +1,6 @@
>>  #include "test-lib.h"
>>  #include "strbuf.h"
>>
>> -/* wrapper that supplies tests with an empty, initialized strbuf */
>> -static void setup(void (*f)(struct strbuf*, const void*),
>> -		  const void *data)
>> -{
>> -	struct strbuf buf = STRBUF_INIT;
>> -
>> -	f(&buf, data);
>> -	strbuf_release(&buf);
>> -	check_uint(buf.len, ==, 0);
>> -	check_uint(buf.alloc, ==, 0);
>> -}
>> -
>> -/* wrapper that supplies tests with a populated, initialized strbuf */
>> -static void setup_populated(void (*f)(struct strbuf*, const void*),
>> -			    const char *init_str, const void *data)
>> -{
>> -	struct strbuf buf = STRBUF_INIT;
>> -
>> -	strbuf_addstr(&buf, init_str);
>> -	check_uint(buf.len, ==, strlen(init_str));
>> -	f(&buf, data);
>> -	strbuf_release(&buf);
>> -	check_uint(buf.len, ==, 0);
>> -	check_uint(buf.alloc, ==, 0);
>> -}
>> -
>>  static int assert_sane_strbuf(struct strbuf *buf)
>>  {
>>  	/* Initialized strbufs should always have a non-NULL buffer */
>> @@ -66,10 +40,8 @@ static void t_dynamic_init(void)
>>  	strbuf_release(&buf);
>>  }
>>
>> -static void t_addch(struct strbuf *buf, const void *data)
>> +static void t_addch(struct strbuf *buf, int ch)
>>  {
>> -	const char *p_ch = data;
>> -	const char ch = *p_ch;
>>  	size_t orig_alloc = buf->alloc;
>>  	size_t orig_len = buf->len;
>>
>> @@ -85,9 +57,8 @@ static void t_addch(struct strbuf *buf, const void *data)
>>  	check_char(buf->buf[buf->len], ==, '\0');
>>  }
>>
>> -static void t_addstr(struct strbuf *buf, const void *data)
>> +static void t_addstr(struct strbuf *buf, const char *text)
>>  {
>> -	const char *text = data;
>>  	size_t len = strlen(text);
>>  	size_t orig_alloc = buf->alloc;
>>  	size_t orig_len = buf->len;
>> @@ -105,18 +76,50 @@ static void t_addstr(struct strbuf *buf, const void *data)
>>  	check_str(buf->buf + orig_len, text);
>>  }
>>
>> +static void t_release(struct strbuf *sb)
>> +{
>> +	strbuf_release(sb);
>> +	check_uint(sb->len, ==, 0);
>> +	check_uint(sb->alloc, ==, 0);
>> +}
>> +
>>  int cmd_main(int argc, const char **argv)
>>  {
>>  	if (!TEST(t_static_init(), "static initialization works"))
>>  		test_skip_all("STRBUF_INIT is broken");
>>  	TEST(t_dynamic_init(), "dynamic initialization works");
>
> IIUC you're leaving t_static_init() as-is so that we can determine
> whether or not to skip the rest of the tests,

Right.  And that place might be a use case for an official version of
test__run_end(), but it's probably better to gather a few more such
candidates before drawing conclusions.

> but is there a reason you
> didn't convert t_dynamic_init() here?

Good question, I don't remember.  Perhaps an oversight or laziness.
Or just a general feeling to leave the init tests alone since the
first one doesn't map to TEST_RUN easily.  So, in a word: No.

>> -	TEST(setup(t_addch, "a"), "strbuf_addch adds char");
>> -	TEST(setup(t_addch, ""), "strbuf_addch adds NUL char");
>> -	TEST(setup_populated(t_addch, "initial value", "a"),
>> -	     "strbuf_addch appends to initial value");
>> -	TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string");
>> -	TEST(setup_populated(t_addstr, "initial value", "hello there"),
>> -	     "strbuf_addstr appends string to initial value");
>> +
>> +	if (TEST_RUN("strbuf_addch adds char")) {
>> +		struct strbuf sb = STRBUF_INIT;
>> +		t_addch(&sb, 'a');
>> +		t_release(&sb);
>> +	}
>> +
>> +	if (TEST_RUN("strbuf_addch adds NUL char")) {
>> +		struct strbuf sb = STRBUF_INIT;
>> +		t_addch(&sb, '\0');
>> +		t_release(&sb);
>> +	}
>> +
>> +	if (TEST_RUN("strbuf_addch appends to initial value")) {
>> +		struct strbuf sb = STRBUF_INIT;
>> +		t_addstr(&sb, "initial value");
>> +		t_addch(&sb, 'a');
>> +		t_release(&sb);
>> +	}
>> +
>> +	if (TEST_RUN("strbuf_addstr adds string")) {
>> +		struct strbuf sb = STRBUF_INIT;
>> +		t_addstr(&sb, "hello there");
>> +		t_release(&sb);
>> +	}
>> +
>> +	if (TEST_RUN("strbuf_addstr appends string to initial value")) {
>> +		struct strbuf sb = STRBUF_INIT;
>> +		t_addstr(&sb, "initial value");
>> +		t_addstr(&sb, "hello there");
>> +		t_release(&sb);
>> +	}
>>
>>  	return test_done();
>>  }
>> --
>> 2.45.2
>
> I think this commit in particular shows how TEST_RUN() is more
> convenient than TEST(). (Although, arguably we shouldn't have allowed
> the setup() + callback situation to start with.)

Great, thanks!

René
Phillip Wood July 2, 2024, 3:14 p.m. UTC | #3
Hi Josh and René

On 01/07/2024 20:58, Josh Steadmon wrote:
> On 2024.06.29 17:47, René Scharfe wrote:
>
> I think this commit in particular shows how TEST_RUN() is more
> convenient than TEST(). (Although, arguably we shouldn't have allowed
> the setup() + callback situation to start with.)

I think the counterargument to that is that using TEST_RUN() makes the 
tests noisier and more error prone because each one has to be wrapped in 
an if() statement and has more boiler plate initializing and freeing the 
strbuf rather than getting that for free by calling the test function 
via setup().

Having said that I don't mind the changes in this patch if that's the 
way others want to go. Getting rid of the untyped test arguments is 
definitely a benefit of this approach.

Best Wishes

Phillip
Ghanshyam Thakkar July 2, 2024, 5:29 p.m. UTC | #4
Josh Steadmon <steadmon@google.com> wrote:
> > -	TEST(setup(t_addch, "a"), "strbuf_addch adds char");
> > -	TEST(setup(t_addch, ""), "strbuf_addch adds NUL char");
> > -	TEST(setup_populated(t_addch, "initial value", "a"),
> > -	     "strbuf_addch appends to initial value");
> > -	TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string");
> > -	TEST(setup_populated(t_addstr, "initial value", "hello there"),
> > -	     "strbuf_addstr appends string to initial value");
> > +
> > +	if (TEST_RUN("strbuf_addch adds char")) {
> > +		struct strbuf sb = STRBUF_INIT;
> > +		t_addch(&sb, 'a');
> > +		t_release(&sb);
> > +	}
> > +
> > +	if (TEST_RUN("strbuf_addch adds NUL char")) {
> > +		struct strbuf sb = STRBUF_INIT;
> > +		t_addch(&sb, '\0');
> > +		t_release(&sb);
> > +	}
> > +
> > +	if (TEST_RUN("strbuf_addch appends to initial value")) {
> > +		struct strbuf sb = STRBUF_INIT;
> > +		t_addstr(&sb, "initial value");
> > +		t_addch(&sb, 'a');
> > +		t_release(&sb);
> > +	}
> > +
> > +	if (TEST_RUN("strbuf_addstr adds string")) {
> > +		struct strbuf sb = STRBUF_INIT;
> > +		t_addstr(&sb, "hello there");
> > +		t_release(&sb);
> > +	}
> > +
> > +	if (TEST_RUN("strbuf_addstr appends string to initial value")) {
> > +		struct strbuf sb = STRBUF_INIT;
> > +		t_addstr(&sb, "initial value");
> > +		t_addstr(&sb, "hello there");
> > +		t_release(&sb);
> > +	}
> > 
> >  	return test_done();
> >  }
> > --
> > 2.45.2
>
> I think this commit in particular shows how TEST_RUN() is more
> convenient than TEST(). (Although, arguably we shouldn't have allowed
> the setup() + callback situation to start with.)

Could you expand a bit on why the setup() + callback thing shouldn't be
allowed? I think it is a nice way of avoiding boilerplate and having
independent state. And, I see the true potential of TEST_RUN() in
testcases defined through macros rather than replacing functions. I
actually think that the previous version with the functions was not
particularly bad, and I agree with Phillip that the previous version's
main() provided nice overview of the tests and it was easier to
verify the independence between each testcase.

Perhaps, the code snippets inside the functions are small enough to
perceive TEST_RUN() as more convenient than TEST() in this test, but,
for future reference, I definitely don't think TEST_RUN() should be
looked at as a replacement for TEST(), and more like 'when we have to
use macro magic which requires us to use internal test__run_*
functions, using TEST_RUN() is more convenient'. Patch [3/6] is a good
example of that. But, I also don't mind if patches 4, 5, or 6 get
merged as I don't see any difference between using TEST_RUN() or
TEST() in those patches, besides moving everything inside main().

Thanks.
René Scharfe July 2, 2024, 8:55 p.m. UTC | #5
Am 02.07.24 um 17:14 schrieb phillip.wood123@gmail.com:
> Hi Josh and René
>
> On 01/07/2024 20:58, Josh Steadmon wrote:
>> On 2024.06.29 17:47, René Scharfe wrote:
>>
>> I think this commit in particular shows how TEST_RUN() is more
>> convenient than TEST(). (Although, arguably we shouldn't have allowed
>> the setup() + callback situation to start with.)
>
> I think the counterargument to that is that using TEST_RUN() makes
> the tests noisier and more error prone because each one has to be
> wrapped in an if() statement and has more boiler plate initializing
> and freeing the strbuf rather than getting that for free by calling
> the test function via setup().

I guess these are two sides of the same coin.  Explicit initialization
and cleanup is closer to what real strbuf users do, more idiomatic.
Which helps to see what the tests are actually doing.

The wrapping if is less annoying than a wrapping function that I have
to name and call.  The condition is just !ctx.skip_all, though, (for
now at least) so we could do something like:

   #define TEST_START(...) if (!TEST_RUN(__VA_ARGS__)) return test_done()

... and tests could look like that then:

   TEST_START("this and that");
   check(this);
   check(that);

   TEST_START("whatever");
   check(whatever);

Perhaps a bit too much magic, though?

René
René Scharfe July 2, 2024, 8:55 p.m. UTC | #6
Am 02.07.24 um 19:29 schrieb Ghanshyam Thakkar:
> Josh Steadmon <steadmon@google.com> wrote:
>>> -	TEST(setup(t_addch, "a"), "strbuf_addch adds char");
>>> -	TEST(setup(t_addch, ""), "strbuf_addch adds NUL char");
>>> -	TEST(setup_populated(t_addch, "initial value", "a"),
>>> -	     "strbuf_addch appends to initial value");
>>> -	TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string");
>>> -	TEST(setup_populated(t_addstr, "initial value", "hello there"),
>>> -	     "strbuf_addstr appends string to initial value");
>>> +
>>> +	if (TEST_RUN("strbuf_addch adds char")) {
>>> +		struct strbuf sb = STRBUF_INIT;
>>> +		t_addch(&sb, 'a');
>>> +		t_release(&sb);
>>> +	}
>>> +
>>> +	if (TEST_RUN("strbuf_addch adds NUL char")) {
>>> +		struct strbuf sb = STRBUF_INIT;
>>> +		t_addch(&sb, '\0');
>>> +		t_release(&sb);
>>> +	}
>>> +
>>> +	if (TEST_RUN("strbuf_addch appends to initial value")) {
>>> +		struct strbuf sb = STRBUF_INIT;
>>> +		t_addstr(&sb, "initial value");
>>> +		t_addch(&sb, 'a');
>>> +		t_release(&sb);
>>> +	}
>>> +
>>> +	if (TEST_RUN("strbuf_addstr adds string")) {
>>> +		struct strbuf sb = STRBUF_INIT;
>>> +		t_addstr(&sb, "hello there");
>>> +		t_release(&sb);
>>> +	}
>>> +
>>> +	if (TEST_RUN("strbuf_addstr appends string to initial value")) {
>>> +		struct strbuf sb = STRBUF_INIT;
>>> +		t_addstr(&sb, "initial value");
>>> +		t_addstr(&sb, "hello there");
>>> +		t_release(&sb);
>>> +	}
>>>
>>>  	return test_done();
>>>  }
>>> --
>>> 2.45.2
>>
>> I think this commit in particular shows how TEST_RUN() is more
>> convenient than TEST(). (Although, arguably we shouldn't have allowed
>> the setup() + callback situation to start with.)
>
> Could you expand a bit on why the setup() + callback thing shouldn't be
> allowed? I think it is a nice way of avoiding boilerplate and having
> independent state. And, I see the true potential of TEST_RUN() in
> testcases defined through macros rather than replacing functions. I
> actually think that the previous version with the functions was not
> particularly bad, and I agree with Phillip that the previous version's
> main() provided nice overview of the tests and it was easier to
> verify the independence between each testcase.

Each test uses its own strbuf and the t_ functions don't use global or
static variables, so how does the doubt about their independence creep
in?

With tests specified as above I now notice that we never check the
resulting string.  t_addch and t_addstr check the tail matches their
argument, but strbuf_addch and strbuf_addstr could overwrite the old
content and we wouldn't notice.  Perhaps it's not worth checking, but
now that tests look more like test_expect_success and all steps are
visible I somehow miss the equivalent of test_cmp; didn't see that
in the original version.

> Perhaps, the code snippets inside the functions are small enough to
> perceive TEST_RUN() as more convenient than TEST() in this test, but,
> for future reference, I definitely don't think TEST_RUN() should be
> looked at as a replacement for TEST(), and more like 'when we have to
> use macro magic which requires us to use internal test__run_*
> functions, using TEST_RUN() is more convenient. Patch [3/6] is a good> example of that.

Sure, not all tests will be improved by using TEST_RUN.  If TEST fits
better, use it.

> But, I also don't mind if patches 4, 5, or 6 get
> merged as I don't see any difference between using TEST_RUN() or
> TEST() in those patches, besides moving everything inside main().

The difference is that in the original version test description and
definition are separated, only linked by a function name.  The new
version brings them together and does away with function name.  A small
change, for sure, just to get rid of the artificial divide and the need
for that link.

René
Ghanshyam Thakkar July 3, 2024, 3:42 a.m. UTC | #7
René Scharfe <l.s.r@web.de> wrote:
> Am 02.07.24 um 19:29 schrieb Ghanshyam Thakkar:
> > Josh Steadmon <steadmon@google.com> wrote:
> >>> -	TEST(setup(t_addch, "a"), "strbuf_addch adds char");
> >>> -	TEST(setup(t_addch, ""), "strbuf_addch adds NUL char");
> >>> -	TEST(setup_populated(t_addch, "initial value", "a"),
> >>> -	     "strbuf_addch appends to initial value");
> >>> -	TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string");
> >>> -	TEST(setup_populated(t_addstr, "initial value", "hello there"),
> >>> -	     "strbuf_addstr appends string to initial value");
> >>> +
> >>> +	if (TEST_RUN("strbuf_addch adds char")) {
> >>> +		struct strbuf sb = STRBUF_INIT;
> >>> +		t_addch(&sb, 'a');
> >>> +		t_release(&sb);
> >>> +	}
> >>> +
> >>> +	if (TEST_RUN("strbuf_addch adds NUL char")) {
> >>> +		struct strbuf sb = STRBUF_INIT;
> >>> +		t_addch(&sb, '\0');
> >>> +		t_release(&sb);
> >>> +	}
> >>> +
> >>> +	if (TEST_RUN("strbuf_addch appends to initial value")) {
> >>> +		struct strbuf sb = STRBUF_INIT;
> >>> +		t_addstr(&sb, "initial value");
> >>> +		t_addch(&sb, 'a');
> >>> +		t_release(&sb);
> >>> +	}
> >>> +
> >>> +	if (TEST_RUN("strbuf_addstr adds string")) {
> >>> +		struct strbuf sb = STRBUF_INIT;
> >>> +		t_addstr(&sb, "hello there");
> >>> +		t_release(&sb);
> >>> +	}
> >>> +
> >>> +	if (TEST_RUN("strbuf_addstr appends string to initial value")) {
> >>> +		struct strbuf sb = STRBUF_INIT;
> >>> +		t_addstr(&sb, "initial value");
> >>> +		t_addstr(&sb, "hello there");
> >>> +		t_release(&sb);
> >>> +	}
> >>>
> >>>  	return test_done();
> >>>  }
> >>> --
> >>> 2.45.2
> >>
> >> I think this commit in particular shows how TEST_RUN() is more
> >> convenient than TEST(). (Although, arguably we shouldn't have allowed
> >> the setup() + callback situation to start with.)
> >
> > Could you expand a bit on why the setup() + callback thing shouldn't be
> > allowed? I think it is a nice way of avoiding boilerplate and having
> > independent state. And, I see the true potential of TEST_RUN() in
> > testcases defined through macros rather than replacing functions. I
> > actually think that the previous version with the functions was not
> > particularly bad, and I agree with Phillip that the previous version's
> > main() provided nice overview of the tests and it was easier to
> > verify the independence between each testcase.
>
> Each test uses its own strbuf and the t_ functions don't use global or
> static variables, so how does the doubt about their independence creep
> in?

Ah, apologies. I should clarify that I meant in general terms about the
future uses of TEST_RUN() and not about this particular patch. But I see
it being less of a problem now that I think about it more. And for the
record, I see no problems in this patch. But on a side note, with what
Phillip was suggesting to remove having TEST_RUN() inside if(), it
would definitely make verifying state independence more harder.

<snip>
> > But, I also don't mind if patches 4, 5, or 6 get
> > merged as I don't see any difference between using TEST_RUN() or
> > TEST() in those patches, besides moving everything inside main().
>
> The difference is that in the original version test description and
> definition are separated, only linked by a function name. The new
> version brings them together and does away with function name. A small
> change, for sure, just to get rid of the artificial divide and the need
> for that link.

Yeah, but I didn't mind that divide (and I don't mind bringing them
together either). :)

Thanks.
diff mbox series

Patch

diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c
index 6027dafef7..c8e39ddda7 100644
--- a/t/unit-tests/t-strbuf.c
+++ b/t/unit-tests/t-strbuf.c
@@ -1,32 +1,6 @@ 
 #include "test-lib.h"
 #include "strbuf.h"

-/* wrapper that supplies tests with an empty, initialized strbuf */
-static void setup(void (*f)(struct strbuf*, const void*),
-		  const void *data)
-{
-	struct strbuf buf = STRBUF_INIT;
-
-	f(&buf, data);
-	strbuf_release(&buf);
-	check_uint(buf.len, ==, 0);
-	check_uint(buf.alloc, ==, 0);
-}
-
-/* wrapper that supplies tests with a populated, initialized strbuf */
-static void setup_populated(void (*f)(struct strbuf*, const void*),
-			    const char *init_str, const void *data)
-{
-	struct strbuf buf = STRBUF_INIT;
-
-	strbuf_addstr(&buf, init_str);
-	check_uint(buf.len, ==, strlen(init_str));
-	f(&buf, data);
-	strbuf_release(&buf);
-	check_uint(buf.len, ==, 0);
-	check_uint(buf.alloc, ==, 0);
-}
-
 static int assert_sane_strbuf(struct strbuf *buf)
 {
 	/* Initialized strbufs should always have a non-NULL buffer */
@@ -66,10 +40,8 @@  static void t_dynamic_init(void)
 	strbuf_release(&buf);
 }

-static void t_addch(struct strbuf *buf, const void *data)
+static void t_addch(struct strbuf *buf, int ch)
 {
-	const char *p_ch = data;
-	const char ch = *p_ch;
 	size_t orig_alloc = buf->alloc;
 	size_t orig_len = buf->len;

@@ -85,9 +57,8 @@  static void t_addch(struct strbuf *buf, const void *data)
 	check_char(buf->buf[buf->len], ==, '\0');
 }

-static void t_addstr(struct strbuf *buf, const void *data)
+static void t_addstr(struct strbuf *buf, const char *text)
 {
-	const char *text = data;
 	size_t len = strlen(text);
 	size_t orig_alloc = buf->alloc;
 	size_t orig_len = buf->len;
@@ -105,18 +76,50 @@  static void t_addstr(struct strbuf *buf, const void *data)
 	check_str(buf->buf + orig_len, text);
 }

+static void t_release(struct strbuf *sb)
+{
+	strbuf_release(sb);
+	check_uint(sb->len, ==, 0);
+	check_uint(sb->alloc, ==, 0);
+}
+
 int cmd_main(int argc, const char **argv)
 {
 	if (!TEST(t_static_init(), "static initialization works"))
 		test_skip_all("STRBUF_INIT is broken");
 	TEST(t_dynamic_init(), "dynamic initialization works");
-	TEST(setup(t_addch, "a"), "strbuf_addch adds char");
-	TEST(setup(t_addch, ""), "strbuf_addch adds NUL char");
-	TEST(setup_populated(t_addch, "initial value", "a"),
-	     "strbuf_addch appends to initial value");
-	TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string");
-	TEST(setup_populated(t_addstr, "initial value", "hello there"),
-	     "strbuf_addstr appends string to initial value");
+
+	if (TEST_RUN("strbuf_addch adds char")) {
+		struct strbuf sb = STRBUF_INIT;
+		t_addch(&sb, 'a');
+		t_release(&sb);
+	}
+
+	if (TEST_RUN("strbuf_addch adds NUL char")) {
+		struct strbuf sb = STRBUF_INIT;
+		t_addch(&sb, '\0');
+		t_release(&sb);
+	}
+
+	if (TEST_RUN("strbuf_addch appends to initial value")) {
+		struct strbuf sb = STRBUF_INIT;
+		t_addstr(&sb, "initial value");
+		t_addch(&sb, 'a');
+		t_release(&sb);
+	}
+
+	if (TEST_RUN("strbuf_addstr adds string")) {
+		struct strbuf sb = STRBUF_INIT;
+		t_addstr(&sb, "hello there");
+		t_release(&sb);
+	}
+
+	if (TEST_RUN("strbuf_addstr appends string to initial value")) {
+		struct strbuf sb = STRBUF_INIT;
+		t_addstr(&sb, "initial value");
+		t_addstr(&sb, "hello there");
+		t_release(&sb);
+	}

 	return test_done();
 }