Re: [PATCH] lib: Convert test_printf.c to KUnit
From: Arpitha Raghunandan
Date: Fri Aug 21 2020 - 00:54:47 EST
On 17/08/20 12:36 pm, Rasmus Villemoes wrote:
> On 17/08/2020 06.30, Arpitha Raghunandan wrote:
>> Converts test lib/test_printf.c to KUnit.
>> More information about KUnit can be found at
>> https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html.
>> KUnit provides a common framework for unit tests in the kernel.
>
> So I can continue to build a kernel with some appropriate CONFIG set to
> y, boot it under virt-me, run dmesg and see if I broke printf? That's
> what I do now, and I don't want to have to start using some enterprisy
> framework.
>
Yes, the test can be run on boot up. More information about this can be found here: https://www.kernel.org/doc/html/latest/dev-tools/kunit/start.html#running-tests-without-the-kunit-wrapper.
>> diff --git a/lib/test_printf.c b/lib/printf_kunit.c
>> similarity index 45%
>> rename from lib/test_printf.c
>> rename to lib/printf_kunit.c
>> index 7ac87f18a10f..68ac5f9b8d28 100644
>> --- a/lib/test_printf.c
>> +++ b/lib/printf_kunit.c
>> @@ -5,6 +5,7 @@
>>
>> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>
>> +#include <kunit/test.h>
>> #include <linux/init.h>
>> #include <linux/kernel.h>
>> #include <linux/module.h>
>> @@ -30,79 +31,61 @@
>> #define PAD_SIZE 16
>> #define FILL_CHAR '$'
>>
>> -static unsigned total_tests __initdata;
>> -static unsigned failed_tests __initdata;
>> -static char *test_buffer __initdata;
>> -static char *alloced_buffer __initdata;
>> +static char *test_buffer;
>> +static char *alloced_buffer;
>>
>> -static int __printf(4, 0) __init
>> -do_test(int bufsize, const char *expect, int elen,
>> +static void __printf(5, 0)
>> +do_test(struct kunit *kunittest, int bufsize, const char *expect, int elen,
>> const char *fmt, va_list ap)
>> {
>> va_list aq;
>> int ret, written;
>>
>> - total_tests++;
>> -
>> memset(alloced_buffer, FILL_CHAR, BUF_SIZE + 2*PAD_SIZE);
>> va_copy(aq, ap);
>> ret = vsnprintf(test_buffer, bufsize, fmt, aq);
>> va_end(aq);
>>
>> - if (ret != elen) {
>> - pr_warn("vsnprintf(buf, %d, \"%s\", ...) returned %d, expected %d\n",
>> + KUNIT_EXPECT_EQ_MSG(kunittest, ret, elen,
>> + "vsnprintf(buf, %d, \"%s\", ...) returned %d, expected %d\n",
>> bufsize, fmt, ret, elen);
>> - return 1;
>> - }
>
>
> IIRC, some of these early returns are required to ensure the following
> checks do not fail (as in, potentially crash the kernel) simply because
> they go off into the weeds. Please double-check that they are all safe
> to continue to perform (though, another reason I might have put them in
> is to simply avoid lots of useless collateral).
>
These are safe to perform. I will check once again though.
>
>> - if (memchr_inv(alloced_buffer, FILL_CHAR, PAD_SIZE)) {
>> + KUNIT_EXPECT_EQ_MSG(kunittest, memchr_inv(alloced_buffer, FILL_CHAR, PAD_SIZE), NULL,
>
>> - if (memchr_inv(test_buffer, FILL_CHAR, BUF_SIZE + PAD_SIZE)) {
>> + KUNIT_EXPECT_FALSE_MSG(kunittest,
>
>> - if (memchr_inv(test_buffer + written + 1, FILL_CHAR, BUF_SIZE + PAD_SIZE - (written + 1))) {
>> + KUNIT_EXPECT_FALSE_MSG(kunittest,
>> + memchr_inv(test_buffer + written + 1, FILL_CHAR, BUF_SIZE + PAD_SIZE - (written + 1))
>
> Why the inconsistency in what a memchr_inv != NULL check gets converted to?
>
Oh my bad. I will make this consistent.
>
>>
>> -static void __printf(3, 4) __init
>> -__test(const char *expect, int elen, const char *fmt, ...)
>> +static void __printf(4, 5)
>> +__test(struct kunit *kunittest, const char *expect, int elen, const char *fmt, ...)
>> {
>> va_list ap;
>> int rand;
>> char *p;
>>
>> - if (elen >= BUF_SIZE) {
>> - pr_err("error in test suite: expected output length %d too long. Format was '%s'.\n",
>> - elen, fmt);
>> - failed_tests++;
>> - return;
>> - }
>> + KUNIT_EXPECT_LT_MSG(kunittest, elen, BUF_SIZE,
>> + "error in test suite: expected output length %d too long. Format was '%s'.\n",
>> + elen, fmt);
>
> And it's ok to continue with the tests when the test suite itself is
> buggy because? [*]
>
>> va_start(ap, fmt);
>>
>> @@ -112,49 +95,46 @@ __test(const char *expect, int elen, const char *fmt, ...)
>> * enough and 0), and then we also test that kvasprintf would
>> * be able to print it as expected.
>> */
>> - failed_tests += do_test(BUF_SIZE, expect, elen, fmt, ap);
>> + do_test(kunittest, BUF_SIZE, expect, elen, fmt, ap);
>> rand = 1 + prandom_u32_max(elen+1);
>> /* Since elen < BUF_SIZE, we have 1 <= rand <= BUF_SIZE. */
>> - failed_tests += do_test(rand, expect, elen, fmt, ap);
>
> [*] Certainly this invariant gets violated, so we (may) provide do_test
> with a buffer size larger than, well, BUF_SIZE.
>
>>
>> -#define test(expect, fmt, ...) \
>> - __test(expect, strlen(expect), fmt, ##__VA_ARGS__)
>> +#define test(kunittest, expect, fmt, ...) \
>> + __test(kunittest, expect, strlen(expect), fmt, ##__VA_ARGS__)
>>
>> -static void __init
>> -test_basic(void)
>> +static void
>> +test_basic(struct kunit *kunittest)
>> {
>> /* Work around annoying "warning: zero-length gnu_printf format string". */
>> char nul = '\0';
>>
>> - test("", &nul);
>> - test("100%", "100%%");
>> - test("xxx%yyy", "xxx%cyyy", '%');
>> - __test("xxx\0yyy", 7, "xxx%cyyy", '\0');
>> + test(kunittest, "", &nul);
>> + test(kunittest, "100%", "100%%");
>> + test(kunittest, "xxx%yyy", "xxx%cyyy", '%');
>> + __test(kunittest, "xxx\0yyy", 7, "xxx%cyyy", '\0');
>
> Am I reading this right that all this is simply to prepend kunittest to
> the arguments? How about just redefining the test macro so it
> automagically does that instead of all this churn? The few cases that
> use __test may need to be handled specially.
>
>> +
>> +static void selftest(struct kunit *kunittest)
>> {
>> alloced_buffer = kmalloc(BUF_SIZE + 2*PAD_SIZE, GFP_KERNEL);
>> if (!alloced_buffer)
>> return;
>> test_buffer = alloced_buffer + PAD_SIZE;
>>
>> - test_basic();
>> - test_number();
>> - test_string();
>> - test_pointer();
>> + test_basic(kunittest);
>> + test_number(kunittest);
>> + test_string(kunittest);
>> + test_pointer(kunittest);
>>
>> kfree(alloced_buffer);
>> }
>
> Even better, since the whole thing still relies on the static variables
> test_buffer and alloced_buffer, why not just stash the struct kunit*
> that the framework passes in a file-scope static and avoid even more
> churn? Then only the newly introduce KUNIT_CHECK_* macros need to refer
> to it, and none of the existing code (or future cases) needs that piece
> of boilerplate.
>
Yes, using file-scope static will be better. I will make this change.
> BTW, does the framework have some kind of logic that ensures nobody runs
> the printf suite twice in parallel?
>
Brendan would have a better idea about this. But, it wouldn't be possible at boot up because KUnit only dispatches each test once. The other way for a KUnit test to be executed currently is as a module, and a module can only be loaded once until it is unloaded.
Thanks for the review.