[Linux-kernel-mentees] [PATCH] lib: kunit_test_overflow: add KUnit test of check_*_overflow functions

Kees Cook keescook at chromium.org
Fri Jun 12 22:36:43 UTC 2020


On Thu, Jun 11, 2020 at 06:55:01PM -0300, Vitor Massaru Iha wrote:
> This adds the convertion of the runtime tests of check_*_overflow fuctions,
> from `lib/test_overflow.c`to KUnit tests.
>
> The log similar to the one seen in dmesg running test_overflow can be
> seen in `test.log`.
>
> Signed-off-by: Vitor Massaru Iha <vitor at massaru.org>
> ---
>  lib/Kconfig.debug         |  17 ++
>  lib/Makefile              |   1 +
>  lib/kunit_overflow_test.c | 590 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 608 insertions(+)
>  create mode 100644 lib/kunit_overflow_test.c

What tree is this based on? I can't apply it to v5.7, linux-next, nor
Linus's latest. I've fixed it up to apply to linux-next for now. :)

Looking at linux-next, though, I am reminded of my agony over naming:

obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += kunit_overflow_test.o

*-test
test_*
*_test

This has to get fixed now, and the naming convention needs to be
documented. For old tests, the preferred naming was test_*. For kunit, I
think it should be kunit_* (and no trailing _test; that's redundant).

For for this bikeshed, I think it should be kunit_overflow.c

For the CONFIG name, it seems to be suggested in docs to be
*_KUNIT_TEST:

...
menuconfig). From there, you can enable any KUnit tests you want: they usually
have config options ending in ``_KUNIT_TEST``.
...

I think much stronger language needs to be added to "Writing your first
test" (which actually recommends the wrong thing: "config
MISC_EXAMPLE_TEST"). And then doesn't specify a module file name, though
it hints at one:

...
        obj-$(CONFIG_MISC_EXAMPLE_TEST) += example-test.o
...

So, please, let's get this documented: we really really need a single
naming convention for these.

For Kconfig in the tree, I see:

drivers/base/Kconfig:config PM_QOS_KUNIT_TEST
drivers/base/test/Kconfig:config KUNIT_DRIVER_PE_TEST
fs/ext4/Kconfig:config EXT4_KUNIT_TESTS
lib/Kconfig.debug:config SYSCTL_KUNIT_TEST
lib/Kconfig.debug:config OVERFLOW_KUNIT_TEST
lib/Kconfig.debug:config LIST_KUNIT_TEST
lib/Kconfig.debug:config LINEAR_RANGES_TEST
lib/kunit/Kconfig:menuconfig KUNIT
lib/kunit/Kconfig:config KUNIT_DEBUGFS
lib/kunit/Kconfig:config KUNIT_TEST
lib/kunit/Kconfig:config KUNIT_EXAMPLE_TEST
lib/kunit/Kconfig:config KUNIT_ALL_TESTS

Which is:

*_KUNIT_TEST
KUNIT_*_TEST
KUNIT_*_TESTS
*_TEST

Nooooo. ;)

If it should all be *_KUNIT_TEST, let's do that. I think just *_KUNIT
would be sufficient (again, adding the word "test" to "kunit" is
redundant). And it absolutely should not be a prefix, otherwise it'll
get sorted away from the thing it's named after. So my preference is
here would be CONFIG_OVERFLOW_KUNIT. (Yes the old convention was
CONFIG_TEST_*, but those things tended to be regression tests, not unit
tests.)

Please please, can we fix this before we add anything more?

>
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index 1f4ab7a2bdee..72fcfe1f24a4 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2075,6 +2075,23 @@ config SYSCTL_KUNIT_TEST
>
>  	  If unsure, say N.
>
> +config OVERFLOW_KUNIT_TEST
> +	tristate "KUnit test for overflow" if !KUNIT_ALL_TESTS
> +	depends on KUNIT
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This builds the overflow KUnit tests.
> +
> +	  KUnit tests run during boot and output the results to the debug log
> +	  in TAP format (http://testanything.org/). Only useful for kernel devs
> +	  running KUnit test harness and are not for inclusion into a production
> +	  build.
> +
> +	  For more information on KUnit and unit tests in general please refer
> +	  to the KUnit documentation in Documentation/dev-tools/kunit/.
> +
> +	  If unsure, say N.
> +
>  config LIST_KUNIT_TEST
>  	tristate "KUnit Test for Kernel Linked-list structures" if !KUNIT_ALL_TESTS
>  	depends on KUNIT

Regarding output:

[   36.611358] TAP version 14
[   36.611953]     # Subtest: overflow
[   36.611954]     1..3
...
[   36.622914]     # overflow_calculation_test: s64: 21 arithmetic tests
[   36.624020]     ok 1 - overflow_calculation_test
...
[   36.731096]     # overflow_shift_test: ok: (s64)(0 << 63) == 0
[   36.731840]     ok 2 - overflow_shift_test
...
[   36.750294] kunit_try_catch: vmalloc: allocation failure: 18446744073709551615 bytes, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0
...
[   36.805350]     # overflow_allocation_test: devm_kzalloc detected saturation
[   36.807763]     ok 3 - overflow_allocation_test
[   36.807765] ok 1 - overflow

A few things here....

- On the outer test report, there is no "plan" line (I was expecting
  "1..1"). Technically it's optional, but it seems like the information
  is available. :)

- The subtest should have its own "TAP version 14" line, and it should
  be using the diagnostic line prefix for the top-level test (this is
  what kselftest is doing).

- There is no way to distinguish top-level TAP output from kernel log
  lines. I think we should stick with the existing marker, which is
  "# ", so that kernel output has no way to be interpreted as TAP
  details -- unless it intentionally starts adding "#"s. ;)

- There is no summary line (to help humans). For example, the kselftest
  API produces a final pass/fail report.

Taken together, I was expecting the output to be:

[   36.611358] # TAP version 14
[   36.611953] # 1..1
[   36.611958] # # TAP version 14
[   36.611954] # # 1..3
...
[   36.622914] # # # overflow_calculation_test: s64: 21 arithmetic tests
[   36.624020] # # ok 1 - overflow_calculation_test
...
[   36.731096] # # # overflow_shift_test: ok: (s64)(0 << 63) == 0
[   36.731840] # # ok 2 - overflow_shift_test
...
[   36.750294] kunit_try_catch: vmalloc: allocation failure: 18446744073709551615 bytes, mode:0xcc0(GFP_KERNEL), nodemask=(null),cpuset=/,mems_allowed=0
...
[   36.805350] # # # overflow_allocation_test: devm_kzalloc detected saturation
[   36.807763] # # ok 3 - overflow_allocation_test
[   36.807763] # # # overflow: PASS
[   36.807765] # ok 1 - overflow
[   36.807767] # # kunit: PASS

But I assume there are threads on this that I've not read... :)


Now, speaking to actual behavior, I love it. :) All the tests are there
(and then some -- noted below).

> diff --git a/lib/Makefile b/lib/Makefile
> index 685aee60de1d..a3290adc0019 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -309,3 +309,4 @@ obj-$(CONFIG_OBJAGG) += objagg.o
>
>  # KUnit tests
>  obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
> +obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += kunit_overflow_test.o
> diff --git a/lib/kunit_overflow_test.c b/lib/kunit_overflow_test.c
> new file mode 100644
> index 000000000000..c3eb8f0d3d50
> --- /dev/null
> +++ b/lib/kunit_overflow_test.c

A lot of this file is unchanged, so I would suggest doing this as a
"git mv lib/test_overflow.c lib/kunit_overflow.c" and then put the
changes into the file. Then it should be easier to track git history, etc.

Without this, it's a lot harder to review this patch since I'm just
looking at a 590 new lines. ;) Really, it's a diff (which I'll paste
here for the code review...)

> --- a/lib/test_overflow.c	2020-06-12 14:07:11.161999209 -0700
> +++ b/lib/kunit_overflow_test.c	2020-06-12 14:07:27.950183116 -0700
> @@ -1,17 +1,18 @@
> -// SPDX-License-Identifier: GPL-2.0 OR MIT
> +// SPDX-License-Identifier: GPL-2.0

Please don't change the license.

> +/*
> + *  This code is the conversion of the overflow test in runtime to KUnit tests.
> + */
> +

This can be left off.

>  /*
>   * Test cases for arithmetic overflow checks.
>   */
>  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>  
> +#include <kunit/test.h>
>  #include <linux/device.h>
>  #include <linux/init.h>
> -#include <linux/kernel.h>
>  #include <linux/mm.h>
> -#include <linux/module.h>
>  #include <linux/overflow.h>
> -#include <linux/slab.h>
> -#include <linux/types.h>
>  #include <linux/vmalloc.h>
>  
>  #define DEFINE_TEST_ARRAY(t)			\
> @@ -19,7 +20,7 @@
>  		t a, b;				\
>  		t sum, diff, prod;		\
>  		bool s_of, d_of, p_of;		\
> -	} t ## _tests[] __initconst
> +	} t ## _tests[]

Why drop the __initconst?

>  DEFINE_TEST_ARRAY(u8) = {
>  	{0, 0, 0, 0, 0, false, false, false},
> @@ -44,6 +45,7 @@
>  	{128, 128, 0, 0, 0, true, false, true},
>  	{123, 234, 101, 145, 110, true, true, true},
>  };
> +

Style nit: I'd like to avoid the blank lines here.

>  DEFINE_TEST_ARRAY(u16) = {
>  	{0, 0, 0, 0, 0, false, false, false},
>  	{1, 1, 2, 0, 1, false, false, false},
> @@ -66,6 +68,7 @@
>  	{123, 234, 357, 65425, 28782, false, true, false},
>  	{1234, 2345, 3579, 64425, 10146, false, true, true},
>  };
> +
>  DEFINE_TEST_ARRAY(u32) = {
>  	{0, 0, 0, 0, 0, false, false, false},
>  	{1, 1, 2, 0, 1, false, false, false},
> @@ -163,6 +166,7 @@
>  	{S16_MIN, S16_MIN, 0, 0, 0, true, false, true},
>  	{S16_MAX, S16_MAX, -2, 0, 1, true, false, true},
>  };
> +
>  DEFINE_TEST_ARRAY(s32) = {
>  	{0, 0, 0, 0, 0, false, false, false},
>  
> @@ -186,6 +190,7 @@
>  	{S32_MIN, S32_MIN, 0, 0, 0, true, false, true},
>  	{S32_MAX, S32_MAX, -2, 0, 1, true, false, true},
>  };
> +
>  DEFINE_TEST_ARRAY(s64) = {
>  	{0, 0, 0, 0, 0, false, false, false},
>  
> @@ -215,254 +220,243 @@
>  	{0, -S64_MAX, -S64_MAX, S64_MAX, 0, false, false, false},
>  };
>  
> -#define check_one_op(t, fmt, op, sym, a, b, r, of) do {		\
> -	t _r;							\
> -	bool _of;						\
> -								\
> -	_of = check_ ## op ## _overflow(a, b, &_r);		\
> -	if (_of != of) {					\
> -		pr_warn("expected "fmt" "sym" "fmt		\
> -			" to%s overflow (type %s)\n",		\
> -			a, b, of ? "" : " not", #t);		\
> -		err = 1;					\
> -	}							\
> -	if (_r != r) {						\
> -		pr_warn("expected "fmt" "sym" "fmt" == "	\
> -			fmt", got "fmt" (type %s)\n",		\
> -			a, b, r, _r, #t);			\
> -		err = 1;					\
> -	}							\
> +#define check_one_op(test, t, fmt, op, sym, a, b, r, of) do {		\
> +	t _r;								\
> +	bool _of;							\
> +									\
> +	_of = check_ ## op ## _overflow(a, b, &_r);			\
> +	if (_of != of) {						\
> +		KUNIT_FAIL(test, "Expected "fmt" "sym" "fmt		\
> +			" to%s overflow (type %s)\n",			\
> +			a, b, of ? "" : " not", #t);			\
> +	}								\
> +	if (_r != r) {							\
> +		KUNIT_FAIL(test, "Expected "fmt" "sym" "fmt" == "	\
> +			fmt", got "fmt" (type %s)\n",			\
> +			a, b, r, _r, #t);				\
> +	}								\
>  } while (0)

The trailing \ makes this more awkward to diff, but one thing I'm not
quite seeing is why "test" needs to be added. It's not a variable in
these macros. i.e. it is used literally:

#define DEFINE_TEST_FUNC(test, t, fmt)						\
static void do_test_ ## t(struct kunit *test, const struct test_ ## t *p)	\
{										\
        check_one_op(test, t, fmt, add, "+", p->a, p->b, p->sum, p->s_of);	\
...

Only callers of the do_test_*() would need to be changed. I think all of
these macros just need the pr_warn/KUNIT_FAIL changes, and the function
prototypes updated to include struct kunit *test.

>  
> -#define DEFINE_TEST_FUNC(t, fmt)					\
> -static int __init do_test_ ## t(const struct test_ ## t *p)		\
> -{							   		\
> -	int err = 0;							\
> -									\
> -	check_one_op(t, fmt, add, "+", p->a, p->b, p->sum, p->s_of);	\
> -	check_one_op(t, fmt, add, "+", p->b, p->a, p->sum, p->s_of);	\
> -	check_one_op(t, fmt, sub, "-", p->a, p->b, p->diff, p->d_of);	\
> -	check_one_op(t, fmt, mul, "*", p->a, p->b, p->prod, p->p_of);	\
> -	check_one_op(t, fmt, mul, "*", p->b, p->a, p->prod, p->p_of);	\
> -									\
> -	return err;							\
> -}									\
> -									\
> -static int __init test_ ## t ## _overflow(void) {			\
> -	int err = 0;							\
> -	unsigned i;							\
> -									\
> -	pr_info("%-3s: %zu arithmetic tests\n", #t,			\
> -		ARRAY_SIZE(t ## _tests));				\
> -	for (i = 0; i < ARRAY_SIZE(t ## _tests); ++i)			\
> -		err |= do_test_ ## t(&t ## _tests[i]);			\
> -	return err;							\
> +#define DEFINE_TEST_FUNC(test, t, fmt)						\
> +static void do_test_ ## t(struct kunit *test, const struct test_ ## t *p)	\
> +{										\
> +	check_one_op(test, t, fmt, add, "+", p->a, p->b, p->sum, p->s_of);	\
> +	check_one_op(test, t, fmt, add, "+", p->b, p->a, p->sum, p->s_of);	\
> +	check_one_op(test, t, fmt, sub, "-", p->a, p->b, p->diff, p->d_of);	\
> +	check_one_op(test, t, fmt, mul, "*", p->a, p->b, p->prod, p->p_of);	\
> +	check_one_op(test, t, fmt, mul, "*", p->b, p->a, p->prod, p->p_of);	\
> +}										\

Then these all only need the prototype on the actual function changed.

> +										\
> +static void  test_ ## t ## _overflow(struct kunit *test)			\
> +{										\
> +	unsigned i;								\
> +										\
> +	kunit_warn(test, "%-3s: %zu arithmetic tests\n", #t,			\
> +		ARRAY_SIZE(t ## _tests));					\
> +	for (i = 0; i < ARRAY_SIZE(t ## _tests); ++i)				\
> +		do_test_ ## t(test, &t ## _tests[i]);				\
>  }
>  
> -DEFINE_TEST_FUNC(u8, "%d");
> -DEFINE_TEST_FUNC(s8, "%d");
> -DEFINE_TEST_FUNC(u16, "%d");
> -DEFINE_TEST_FUNC(s16, "%d");
> -DEFINE_TEST_FUNC(u32, "%u");
> -DEFINE_TEST_FUNC(s32, "%d");
> +DEFINE_TEST_FUNC(test, u8, "%d");
> +DEFINE_TEST_FUNC(test, s8, "%d");
> +DEFINE_TEST_FUNC(test, u16, "%d");
> +DEFINE_TEST_FUNC(test, s16, "%d");
> +DEFINE_TEST_FUNC(test, u32, "%u");
> +DEFINE_TEST_FUNC(test, s32, "%d");
>  #if BITS_PER_LONG == 64
> -DEFINE_TEST_FUNC(u64, "%llu");
> -DEFINE_TEST_FUNC(s64, "%lld");
> +DEFINE_TEST_FUNC(test, u64, "%llu");
> +DEFINE_TEST_FUNC(test, s64, "%lld");
>  #endif

And all the actual uses of the macros can be left unchanged.

>  
> -static int __init test_overflow_calculation(void)
> +static void  overflow_calculation_test(struct kunit *test)
>  {
> -	int err = 0;
>  
> -	err |= test_u8_overflow();
> -	err |= test_s8_overflow();
> -	err |= test_u16_overflow();
> -	err |= test_s16_overflow();
> -	err |= test_u32_overflow();
> -	err |= test_s32_overflow();
> +	test_u8_overflow(test);
> +	test_s8_overflow(test);
> +	test_s8_overflow(test);

The s8 test got added twice here accidentally.

> +	test_u16_overflow(test);
> +	test_s16_overflow(test);
> +	test_u32_overflow(test);
> +	test_s32_overflow(test);
>  #if BITS_PER_LONG == 64
> -	err |= test_u64_overflow();
> -	err |= test_s64_overflow();
> +	test_u64_overflow(test);
> +	test_s64_overflow(test);
>  #endif
> -
> -	return err;
>  }

I think it might be nice to keep the "err" vars around for a final report
line (maybe per test)? (It would keep the diff churn way lower, too...)

So, yes! I like it. :) Most of my comments here have nothing to do with
specifically this patch (sorry)! But I'd love to see a v2.

Thanks for doing this! I'm glad to see more TAP output. :)

-- 
Kees Cook


More information about the Linux-kernel-mentees mailing list