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

Vitor Massaru Iha vitor at massaru.org
Mon Jun 15 16:30:42 UTC 2020


On Fri, 2020-06-12 at 15:36 -0700, Kees Cook wrote:
> 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?

Sure, I'll rewrite with _KUNIT_TEST, as David Gow suggested in the next
emails.

> 
> > 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...)
> 

Sure, I'll do it. I didn't know if the runtime tests were going to
stay.

> > --- 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.

Sure I'll fix it.

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

Sure I'll fix it.
> >  /*
> >   * 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?

I removed __initconst because of these warnings below, as it is used
for the kernel during the module initialization, and I do not use the
module initialization in this tests. Does this have any side effects in
these tests?

WARNING: modpost: vmlinux.o(.text.unlikely+0x131b7): Section mismatch
in reference from the function test_s8_overflow() to the variable
.init.rodata:s8_tests
The function test_s8_overflow() references
the variable __initconst s8_tests.
This is often because test_s8_overflow lacks a __initconst 
annotation or the annotation of s8_tests is wrong.


> >  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...)
> 

I will correct these other suggestions.

> 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. :)
> 

Thanks Kees, I'm learning a lot from you, and as I said privately with
Brendan, I've never seen so much macro in a code. I learned a lot from
it.





More information about the Linux-kernel-mentees mailing list