All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
@ 2023-01-24  8:03 David Gow
  2023-01-24 10:43 ` kernel test robot
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: David Gow @ 2023-01-24  8:03 UTC (permalink / raw)
  To: Brendan Higgins, Kees Cook, Shuah Khan, Daniel Latypov, Rae Moar
  Cc: Sadiya Kazi, kunit-dev, linux-kselftest, linux-kernel, David Gow

KUnit has several macros and functions intended for use from non-test
code. These hooks, currently the kunit_get_current_test() and
kunit_fail_current_test() macros, didn't work when CONFIG_KUNIT=m.

In order to support this case, the required functions and static data
need to be available unconditionally, even when KUnit itself is not
built-in. The new 'hooks.c' file is therefore always included, and has
both the static key required for kunit_get_current_test(), and a
function pointer to the real implementation of
__kunit_fail_current_test(), which is populated when the KUnit module is
loaded.

A new header, kunit/hooks-table.h, contains a table of all hooks, and is
repeatedly included with different definitions of the KUNIT_HOOK() in
order to automatically generate the needed function pointer tables. When
KUnit is disabled, or the module is not loaded, these function pointers
are all NULL. This shouldn't be a problem, as they're all used behind
wrappers which check kunit_running and/or that the pointer is non-NULL.

This can then be extended for future features which require similar
"hook" behaviour, such as static stubs:
https://lore.kernel.org/all/20221208061841.2186447-1-davidgow@google.com/

Signed-off-by: David Gow <davidgow@google.com>
---

This is basically a prerequisite for the stub features working when
KUnit is built as a module, and should nicely make a few other tests
work then, too.

v2 adds a slightly-excessive macro-based system for defining hooks. This
made adding the static stub hooks absolutely trivial, and the complexity
is totally hidden from the user (being an internal KUnit implementation
detail), so I'm more comfortable with this than some other macro magic.

It does however result in a huge number of checkpatch.pl errors, as
we're using macros in unconventional ways, and checkpatch just can't
work out the syntax. These are mostly "Macros with complex values should
be enclosed in parentheses", "Macros with multiple statements should be
enclosed in a do - while loop", and similar, which don't apply due to
the macros not being expressions: they are mostly declarations or
assignment statements. There are a few others where checkpatch thinks
that the return value is the function name and similar, so complains
about the style.

Open questions:
- Is this macro-based system worth it, or was v1 better?
- Should we rename test-bug.h to hooks.h or similar.
  (I think so, but would rather do it in a separate patch, to make it
  easier to review. There are a few includes of it scattered about.)
- Is making these NULL when KUnit isn't around sensible, or should we
  auto-generate a "default" implementation. This is a pretty easy
  extension to the macros here.
  (I think NULL is good for now, as we have wrappers for these anyway.
  If we want to change our minds later as we add more hooks, it's easy.)
- Any other thoughts?

Cheers,
-- David

Changes since RFC v1:
https://lore.kernel.org/all/20230117142737.246446-1-davidgow@google.com/
- Major refit to auto-generate the hook code using macros.
- (Note that previous Reviewed-by tags have not been added, as this is a
  big enough change it probably needs a re-reviews. Thanks Rae for
  reviewing RFC v1 previously, though!)
---
 Documentation/dev-tools/kunit/usage.rst | 14 +++++-----
 include/kunit/hooks-table.h             | 34 +++++++++++++++++++++++++
 include/kunit/test-bug.h                | 24 +++++++++--------
 lib/Makefile                            |  4 +++
 lib/kunit/Makefile                      |  3 +++
 lib/kunit/hooks.c                       | 27 ++++++++++++++++++++
 lib/kunit/test.c                        | 22 +++++++++++-----
 7 files changed, 103 insertions(+), 25 deletions(-)
 create mode 100644 include/kunit/hooks-table.h
 create mode 100644 lib/kunit/hooks.c

diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst
index 48f8196d5aad..6424493b93cb 100644
--- a/Documentation/dev-tools/kunit/usage.rst
+++ b/Documentation/dev-tools/kunit/usage.rst
@@ -648,10 +648,9 @@ We can do this via the ``kunit_test`` field in ``task_struct``, which we can
 access using the ``kunit_get_current_test()`` function in ``kunit/test-bug.h``.
 
 ``kunit_get_current_test()`` is safe to call even if KUnit is not enabled. If
-KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
-running in the current task, it will return ``NULL``. This compiles down to
-either a no-op or a static key check, so will have a negligible performance
-impact when no test is running.
+KUnit is not enabled, or if no test is running in the current task, it will
+return ``NULL``. This compiles down to either a no-op or a static key check,
+so will have a negligible performance impact when no test is running.
 
 The example below uses this to implement a "mock" implementation of a function, ``foo``:
 
@@ -726,8 +725,7 @@ structures as shown below:
 	#endif
 
 ``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
-KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
-running in the current task, it will do nothing. This compiles down to either a
-no-op or a static key check, so will have a negligible performance impact when
-no test is running.
+KUnit is not enabled, or if no test is running in the current task, it will do
+nothing. This compiles down to either a no-op or a static key check, so will
+have a negligible performance impact when no test is running.
 
diff --git a/include/kunit/hooks-table.h b/include/kunit/hooks-table.h
new file mode 100644
index 000000000000..0b5eafd199ed
--- /dev/null
+++ b/include/kunit/hooks-table.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * KUnit 'Hooks' function pointer table
+ *
+ * This file is included multiple times, each time with a different definition
+ * of KUNIT_HOOK. This provides one place where all of the hooks can be listed
+ * which can then be converted into function / implementation declarations, or
+ * code to set function pointers.
+ *
+ * Copyright (C) 2023, Google LLC.
+ * Author: David Gow <davidgow@google.com>
+ */
+
+/*
+ * To declare a hook, use:
+ * KUNIT_HOOK(name, retval, args), where:
+ * - name: the function name of the exported hook
+ * - retval: the type of the return value of the hook
+ * - args: the arguments to the hook, of the form (int a, int b)
+ *
+ * Note that the argument list should be contained within the brackets (),
+ * and that the implementation of the hook should be in a <name>_impl
+ * function, which should not be declared static, but need not be exported.
+ */
+
+#ifndef KUNIT_HOOK
+#error KUNIT_HOOK must be defined before including the hooks table
+#endif
+
+KUNIT_HOOK(__kunit_fail_current_test, __printf(3, 4) void,
+	   (const char *file, int line, const char *fmt, ...));
+
+/* Undefine KUNIT_HOOK at the end, ready for the next use. */
+#undef KUNIT_HOOK
diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
index c1b2e14eab64..3203ffc0a08b 100644
--- a/include/kunit/test-bug.h
+++ b/include/kunit/test-bug.h
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /*
- * KUnit API allowing dynamic analysis tools to interact with KUnit tests
+ * KUnit API providing hooks for non-test code to interact with tests.
  *
  * Copyright (C) 2020, Google LLC.
  * Author: Uriel Guajardo <urielguajardo@google.com>
@@ -9,7 +9,7 @@
 #ifndef _KUNIT_TEST_BUG_H
 #define _KUNIT_TEST_BUG_H
 
-#if IS_BUILTIN(CONFIG_KUNIT)
+#if IS_ENABLED(CONFIG_KUNIT)
 
 #include <linux/jump_label.h> /* For static branch */
 #include <linux/sched.h>
@@ -43,20 +43,21 @@ static inline struct kunit *kunit_get_current_test(void)
  * kunit_fail_current_test() - If a KUnit test is running, fail it.
  *
  * If a KUnit test is running in the current task, mark that test as failed.
- *
- * This macro will only work if KUnit is built-in (though the tests
- * themselves can be modules). Otherwise, it compiles down to nothing.
  */
 #define kunit_fail_current_test(fmt, ...) do {					\
 		if (static_branch_unlikely(&kunit_running)) {			\
+			/* Guaranteed to be non-NULL when kunit_running true*/	\
 			__kunit_fail_current_test(__FILE__, __LINE__,		\
 						  fmt, ##__VA_ARGS__);		\
 		}								\
 	} while (0)
 
 
-extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
-						    const char *fmt, ...);
+/* Declare all of the available hooks. */
+#define KUNIT_HOOK(name, retval, args) \
+	extern retval (*name)args
+
+#include "kunit/hooks-table.h"
 
 #else
 
@@ -66,10 +67,11 @@ static inline struct kunit *kunit_get_current_test(void) { return NULL; }
 #define kunit_fail_current_test(fmt, ...) \
 		__kunit_fail_current_test(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
 
-static inline __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
-							    const char *fmt, ...)
-{
-}
+/* No-op stubs if KUnit is not enabled. */
+#define KUNIT_HOOK(name, retval, args) \
+	static retval (*name)args = NULL
+
+#include "kunit/hooks-table.h"
 
 #endif
 
diff --git a/lib/Makefile b/lib/Makefile
index 4d9461bfea42..9031de6ca73c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -126,6 +126,10 @@ CFLAGS_test_fpu.o += $(FPU_CFLAGS)
 obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
 
 obj-$(CONFIG_KUNIT) += kunit/
+# Include the KUnit hooks unconditionally. They'll compile to nothing if
+# CONFIG_KUNIT=n, otherwise will be a small table of static data (static key,
+# function pointers) which need to be built-in even when KUnit is a module.
+obj-y += kunit/hooks.o
 
 ifeq ($(CONFIG_DEBUG_KOBJECT),y)
 CFLAGS_kobject.o += -DDEBUG
diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
index 29aff6562b42..deeb46cc879b 100644
--- a/lib/kunit/Makefile
+++ b/lib/kunit/Makefile
@@ -11,6 +11,9 @@ ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
 kunit-objs +=				debugfs.o
 endif
 
+# KUnit 'hooks' are built-in even when KUnit is built as a module.
+lib-y +=				hooks.o
+
 obj-$(CONFIG_KUNIT_TEST) +=		kunit-test.o
 
 # string-stream-test compiles built-in only.
diff --git a/lib/kunit/hooks.c b/lib/kunit/hooks.c
new file mode 100644
index 000000000000..29e81614f486
--- /dev/null
+++ b/lib/kunit/hooks.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit 'Hooks' implementation.
+ *
+ * This file contains code / structures which should be built-in even when
+ * KUnit itself is built as a module.
+ *
+ * Copyright (C) 2022, Google LLC.
+ * Author: David Gow <davidgow@google.com>
+ */
+
+/* This file is always built-in, so make sure it's empty if CONFIG_KUNIT=n */
+#if IS_ENABLED(CONFIG_KUNIT)
+
+#include <kunit/test-bug.h>
+
+DEFINE_STATIC_KEY_FALSE(kunit_running);
+EXPORT_SYMBOL(kunit_running);
+
+/* Function pointers for hooks. */
+#define KUNIT_HOOK(name, retval, args) \
+	retval (*name)args; \
+	EXPORT_SYMBOL(name)
+
+#include "kunit/hooks-table.h"
+
+#endif
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index c9ebf975e56b..b6c88f722b68 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -20,13 +20,10 @@
 #include "string-stream.h"
 #include "try-catch-impl.h"
 
-DEFINE_STATIC_KEY_FALSE(kunit_running);
-
-#if IS_BUILTIN(CONFIG_KUNIT)
 /*
  * Fail the current test and print an error message to the log.
  */
-void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...)
+void __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...)
 {
 	va_list args;
 	int len;
@@ -53,8 +50,6 @@ void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...)
 	kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer);
 	kunit_kfree(current->kunit_test, buffer);
 }
-EXPORT_SYMBOL_GPL(__kunit_fail_current_test);
-#endif
 
 /*
  * Enable KUnit tests to run.
@@ -775,8 +770,18 @@ void kunit_cleanup(struct kunit *test)
 }
 EXPORT_SYMBOL_GPL(kunit_cleanup);
 
+/* Declarations for the hook implemetnations */
+#define KUNIT_HOOK(name, retval, args) \
+	extern retval name##_impl args
+#include "kunit/hooks-table.h"
+
 static int __init kunit_init(void)
 {
+	/* Install the KUnit hook functions. */
+#define KUNIT_HOOK(name, retval, args) \
+	name = name##_impl
+#include "kunit/hooks-table.h"
+
 	kunit_debugfs_init();
 #ifdef CONFIG_MODULES
 	return register_module_notifier(&kunit_mod_nb);
@@ -788,6 +793,11 @@ late_initcall(kunit_init);
 
 static void __exit kunit_exit(void)
 {
+	/* Remove the KUnit hook functions. */
+#define KUNIT_HOOK(name, retval, args) \
+	name = NULL
+#include "kunit/hooks-table.h"
+
 #ifdef CONFIG_MODULES
 	unregister_module_notifier(&kunit_mod_nb);
 #endif
-- 
2.39.0.246.g2a6d74b583-goog


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
  2023-01-24  8:03 [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module David Gow
@ 2023-01-24 10:43 ` kernel test robot
  2023-01-27  0:48 ` Brendan Higgins
  2023-01-27  5:38 ` Daniel Latypov
  2 siblings, 0 replies; 6+ messages in thread
From: kernel test robot @ 2023-01-24 10:43 UTC (permalink / raw)
  To: David Gow; +Cc: oe-kbuild-all

Hi David,

[FYI, it's a private test report for your RFC patch.]
[auto build test WARNING on kees/for-next/pstore]
[also build test WARNING on kees/for-next/kspp linus/master v6.2-rc5]
[cannot apply to next-20230124]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/David-Gow/kunit-Add-hooks-to-call-into-KUnit-when-it-s-built-as-a-module/20230124-160516
base:   https://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/pstore
patch link:    https://lore.kernel.org/r/20230124080350.2275652-1-davidgow%40google.com
patch subject: [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20230124/202301241805.ieXjH3yy-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/e72363e2b7988bc87fbe76b2083a2736640d2df9
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review David-Gow/kunit-Add-hooks-to-call-into-KUnit-when-it-s-built-as-a-module/20230124-160516
        git checkout e72363e2b7988bc87fbe76b2083a2736640d2df9
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=alpha olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=alpha SHELL=/bin/bash lib/kunit/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> lib/kunit/test.c:26:6: warning: no previous prototype for '__kunit_fail_current_test_impl' [-Wmissing-prototypes]
      26 | void __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...)
         |      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   lib/kunit/test.c: In function '__kunit_fail_current_test_impl':
   lib/kunit/test.c:39:9: warning: function '__kunit_fail_current_test_impl' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format]
      39 |         len = vsnprintf(NULL, 0, fmt, args) + 1;
         |         ^~~
   lib/kunit/test.c:47:9: warning: function '__kunit_fail_current_test_impl' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format]
      47 |         vsnprintf(buffer, len, fmt, args);
         |         ^~~~~~~~~


vim +/__kunit_fail_current_test_impl +26 lib/kunit/test.c

    22	
    23	/*
    24	 * Fail the current test and print an error message to the log.
    25	 */
  > 26	void __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...)
    27	{
    28		va_list args;
    29		int len;
    30		char *buffer;
    31	
    32		if (!current->kunit_test)
    33			return;
    34	
    35		kunit_set_failure(current->kunit_test);
    36	
    37		/* kunit_err() only accepts literals, so evaluate the args first. */
    38		va_start(args, fmt);
    39		len = vsnprintf(NULL, 0, fmt, args) + 1;
    40		va_end(args);
    41	
    42		buffer = kunit_kmalloc(current->kunit_test, len, GFP_KERNEL);
    43		if (!buffer)
    44			return;
    45	
    46		va_start(args, fmt);
    47		vsnprintf(buffer, len, fmt, args);
    48		va_end(args);
    49	
    50		kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer);
    51		kunit_kfree(current->kunit_test, buffer);
    52	}
    53	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
  2023-01-24  8:03 [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module David Gow
  2023-01-24 10:43 ` kernel test robot
@ 2023-01-27  0:48 ` Brendan Higgins
  2023-01-27  1:31   ` David Gow
  2023-01-27  5:38 ` Daniel Latypov
  2 siblings, 1 reply; 6+ messages in thread
From: Brendan Higgins @ 2023-01-27  0:48 UTC (permalink / raw)
  To: David Gow, Shuah Khan
  Cc: Brendan Higgins, Kees Cook, Daniel Latypov, Rae Moar,
	Sadiya Kazi, kunit-dev, linux-kselftest, linux-kernel

On Tue, Jan 24, 2023 at 3:04 AM 'David Gow' via KUnit Development
<kunit-dev@googlegroups.com> wrote:
>
> KUnit has several macros and functions intended for use from non-test
> code. These hooks, currently the kunit_get_current_test() and
> kunit_fail_current_test() macros, didn't work when CONFIG_KUNIT=m.
>
> In order to support this case, the required functions and static data
> need to be available unconditionally, even when KUnit itself is not
> built-in. The new 'hooks.c' file is therefore always included, and has
> both the static key required for kunit_get_current_test(), and a
> function pointer to the real implementation of
> __kunit_fail_current_test(), which is populated when the KUnit module is
> loaded.
>
> A new header, kunit/hooks-table.h, contains a table of all hooks, and is
> repeatedly included with different definitions of the KUNIT_HOOK() in
> order to automatically generate the needed function pointer tables. When
> KUnit is disabled, or the module is not loaded, these function pointers
> are all NULL. This shouldn't be a problem, as they're all used behind
> wrappers which check kunit_running and/or that the pointer is non-NULL.
>
> This can then be extended for future features which require similar
> "hook" behaviour, such as static stubs:
> https://lore.kernel.org/all/20221208061841.2186447-1-davidgow@google.com/

Devilishly clever. Maybe too clever, but I don't have any better ideas, so:

Reviewed-by: Brendan Higgins <brendanhiggins@google.com>

Nevertheless, see my comments below:

> Signed-off-by: David Gow <davidgow@google.com>
> ---
>
> This is basically a prerequisite for the stub features working when
> KUnit is built as a module, and should nicely make a few other tests
> work then, too.
>
> v2 adds a slightly-excessive macro-based system for defining hooks. This
> made adding the static stub hooks absolutely trivial, and the complexity
> is totally hidden from the user (being an internal KUnit implementation
> detail), so I'm more comfortable with this than some other macro magic.
>
> It does however result in a huge number of checkpatch.pl errors, as
> we're using macros in unconventional ways, and checkpatch just can't
> work out the syntax. These are mostly "Macros with complex values should
> be enclosed in parentheses", "Macros with multiple statements should be
> enclosed in a do - while loop", and similar, which don't apply due to
> the macros not being expressions: they are mostly declarations or
> assignment statements. There are a few others where checkpatch thinks
> that the return value is the function name and similar, so complains
> about the style.

Shuah, what are your thoughts here? I think it's OK, but I don't want
to go any further down this path unless you are OK with it too.

> Open questions:
> - Is this macro-based system worth it, or was v1 better?

I think this is definitely better if we had more than one function to
hook. With just one function - I am less confident, but I like having
a set way to do it.

> - Should we rename test-bug.h to hooks.h or similar.
>   (I think so, but would rather do it in a separate patch, to make it
>   easier to review. There are a few includes of it scattered about.)

Agreed, that confused me at first.

> - Is making these NULL when KUnit isn't around sensible, or should we
>   auto-generate a "default" implementation. This is a pretty easy
>   extension to the macros here.
>   (I think NULL is good for now, as we have wrappers for these anyway.
>   If we want to change our minds later as we add more hooks, it's easy.)

Yeah, I'm fine with either.

> - Any other thoughts?

My primary concern was with the naming of test-bug.h, but you said
you'd handle that in another patch, which makes sense to me.

I also want to make sure Shuah is OK with the checkpatch warnings.

I did find two nits below:

> Cheers,
> -- David
>
> Changes since RFC v1:
> https://lore.kernel.org/all/20230117142737.246446-1-davidgow@google.com/
> - Major refit to auto-generate the hook code using macros.
> - (Note that previous Reviewed-by tags have not been added, as this is a
>   big enough change it probably needs a re-reviews. Thanks Rae for
>   reviewing RFC v1 previously, though!)
> ---
>  Documentation/dev-tools/kunit/usage.rst | 14 +++++-----
>  include/kunit/hooks-table.h             | 34 +++++++++++++++++++++++++
>  include/kunit/test-bug.h                | 24 +++++++++--------
>  lib/Makefile                            |  4 +++
>  lib/kunit/Makefile                      |  3 +++
>  lib/kunit/hooks.c                       | 27 ++++++++++++++++++++
>  lib/kunit/test.c                        | 22 +++++++++++-----
>  7 files changed, 103 insertions(+), 25 deletions(-)
>  create mode 100644 include/kunit/hooks-table.h
>  create mode 100644 lib/kunit/hooks.c
>
> diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst
> index 48f8196d5aad..6424493b93cb 100644
> --- a/Documentation/dev-tools/kunit/usage.rst
> +++ b/Documentation/dev-tools/kunit/usage.rst
> @@ -648,10 +648,9 @@ We can do this via the ``kunit_test`` field in ``task_struct``, which we can
>  access using the ``kunit_get_current_test()`` function in ``kunit/test-bug.h``.
>
>  ``kunit_get_current_test()`` is safe to call even if KUnit is not enabled. If
> -KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
> -running in the current task, it will return ``NULL``. This compiles down to
> -either a no-op or a static key check, so will have a negligible performance
> -impact when no test is running.
> +KUnit is not enabled, or if no test is running in the current task, it will
> +return ``NULL``. This compiles down to either a no-op or a static key check,
> +so will have a negligible performance impact when no test is running.
>
>  The example below uses this to implement a "mock" implementation of a function, ``foo``:
>
> @@ -726,8 +725,7 @@ structures as shown below:
>         #endif
>
>  ``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
> -KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
> -running in the current task, it will do nothing. This compiles down to either a
> -no-op or a static key check, so will have a negligible performance impact when
> -no test is running.
> +KUnit is not enabled, or if no test is running in the current task, it will do
> +nothing. This compiles down to either a no-op or a static key check, so will
> +have a negligible performance impact when no test is running.
>
> diff --git a/include/kunit/hooks-table.h b/include/kunit/hooks-table.h
> new file mode 100644
> index 000000000000..0b5eafd199ed
> --- /dev/null
> +++ b/include/kunit/hooks-table.h
> @@ -0,0 +1,34 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KUnit 'Hooks' function pointer table
> + *
> + * This file is included multiple times, each time with a different definition
> + * of KUNIT_HOOK. This provides one place where all of the hooks can be listed
> + * which can then be converted into function / implementation declarations, or
> + * code to set function pointers.
> + *
> + * Copyright (C) 2023, Google LLC.
> + * Author: David Gow <davidgow@google.com>
> + */
> +
> +/*
> + * To declare a hook, use:
> + * KUNIT_HOOK(name, retval, args), where:
> + * - name: the function name of the exported hook
> + * - retval: the type of the return value of the hook
> + * - args: the arguments to the hook, of the form (int a, int b)
> + *
> + * Note that the argument list should be contained within the brackets (),
> + * and that the implementation of the hook should be in a <name>_impl
> + * function, which should not be declared static, but need not be exported.
> + */
> +
> +#ifndef KUNIT_HOOK
> +#error KUNIT_HOOK must be defined before including the hooks table
> +#endif
> +
> +KUNIT_HOOK(__kunit_fail_current_test, __printf(3, 4) void,
> +          (const char *file, int line, const char *fmt, ...));
> +
> +/* Undefine KUNIT_HOOK at the end, ready for the next use. */
> +#undef KUNIT_HOOK
> diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
> index c1b2e14eab64..3203ffc0a08b 100644
> --- a/include/kunit/test-bug.h
> +++ b/include/kunit/test-bug.h
> @@ -1,6 +1,6 @@
>  /* SPDX-License-Identifier: GPL-2.0 */
>  /*
> - * KUnit API allowing dynamic analysis tools to interact with KUnit tests
> + * KUnit API providing hooks for non-test code to interact with tests.
>   *
>   * Copyright (C) 2020, Google LLC.
>   * Author: Uriel Guajardo <urielguajardo@google.com>
> @@ -9,7 +9,7 @@
>  #ifndef _KUNIT_TEST_BUG_H
>  #define _KUNIT_TEST_BUG_H
>
> -#if IS_BUILTIN(CONFIG_KUNIT)
> +#if IS_ENABLED(CONFIG_KUNIT)
>
>  #include <linux/jump_label.h> /* For static branch */
>  #include <linux/sched.h>
> @@ -43,20 +43,21 @@ static inline struct kunit *kunit_get_current_test(void)
>   * kunit_fail_current_test() - If a KUnit test is running, fail it.
>   *
>   * If a KUnit test is running in the current task, mark that test as failed.
> - *
> - * This macro will only work if KUnit is built-in (though the tests
> - * themselves can be modules). Otherwise, it compiles down to nothing.
>   */
>  #define kunit_fail_current_test(fmt, ...) do {                                 \
>                 if (static_branch_unlikely(&kunit_running)) {                   \
> +                       /* Guaranteed to be non-NULL when kunit_running true*/  \
>                         __kunit_fail_current_test(__FILE__, __LINE__,           \
>                                                   fmt, ##__VA_ARGS__);          \
>                 }                                                               \
>         } while (0)
>
>
> -extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
> -                                                   const char *fmt, ...);
> +/* Declare all of the available hooks. */
> +#define KUNIT_HOOK(name, retval, args) \
> +       extern retval (*name)args
> +
> +#include "kunit/hooks-table.h"
>
>  #else
>
> @@ -66,10 +67,11 @@ static inline struct kunit *kunit_get_current_test(void) { return NULL; }
>  #define kunit_fail_current_test(fmt, ...) \
>                 __kunit_fail_current_test(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
>
> -static inline __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
> -                                                           const char *fmt, ...)
> -{
> -}
> +/* No-op stubs if KUnit is not enabled. */
> +#define KUNIT_HOOK(name, retval, args) \
> +       static retval (*name)args = NULL
> +
> +#include "kunit/hooks-table.h"
>
>  #endif
>
> diff --git a/lib/Makefile b/lib/Makefile
> index 4d9461bfea42..9031de6ca73c 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -126,6 +126,10 @@ CFLAGS_test_fpu.o += $(FPU_CFLAGS)
>  obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
>
>  obj-$(CONFIG_KUNIT) += kunit/
> +# Include the KUnit hooks unconditionally. They'll compile to nothing if
> +# CONFIG_KUNIT=n, otherwise will be a small table of static data (static key,
> +# function pointers) which need to be built-in even when KUnit is a module.
> +obj-y += kunit/hooks.o
>
>  ifeq ($(CONFIG_DEBUG_KOBJECT),y)
>  CFLAGS_kobject.o += -DDEBUG
> diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> index 29aff6562b42..deeb46cc879b 100644
> --- a/lib/kunit/Makefile
> +++ b/lib/kunit/Makefile
> @@ -11,6 +11,9 @@ ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
>  kunit-objs +=                          debugfs.o
>  endif
>
> +# KUnit 'hooks' are built-in even when KUnit is built as a module.
> +lib-y +=                               hooks.o
> +
>  obj-$(CONFIG_KUNIT_TEST) +=            kunit-test.o
>
>  # string-stream-test compiles built-in only.
> diff --git a/lib/kunit/hooks.c b/lib/kunit/hooks.c
> new file mode 100644
> index 000000000000..29e81614f486
> --- /dev/null
> +++ b/lib/kunit/hooks.c
> @@ -0,0 +1,27 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KUnit 'Hooks' implementation.
> + *
> + * This file contains code / structures which should be built-in even when
> + * KUnit itself is built as a module.
> + *
> + * Copyright (C) 2022, Google LLC.
> + * Author: David Gow <davidgow@google.com>
> + */
> +
> +/* This file is always built-in, so make sure it's empty if CONFIG_KUNIT=n */
> +#if IS_ENABLED(CONFIG_KUNIT)
> +
> +#include <kunit/test-bug.h>
> +
> +DEFINE_STATIC_KEY_FALSE(kunit_running);
> +EXPORT_SYMBOL(kunit_running);
> +
> +/* Function pointers for hooks. */
> +#define KUNIT_HOOK(name, retval, args) \
> +       retval (*name)args; \
> +       EXPORT_SYMBOL(name)
> +
> +#include "kunit/hooks-table.h"
> +
> +#endif
> diff --git a/lib/kunit/test.c b/lib/kunit/test.c
> index c9ebf975e56b..b6c88f722b68 100644
> --- a/lib/kunit/test.c
> +++ b/lib/kunit/test.c
> @@ -20,13 +20,10 @@
>  #include "string-stream.h"
>  #include "try-catch-impl.h"
>
> -DEFINE_STATIC_KEY_FALSE(kunit_running);
> -
> -#if IS_BUILTIN(CONFIG_KUNIT)
>  /*
>   * Fail the current test and print an error message to the log.
>   */
> -void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...)

nit: I think it would be good to add a comment here about this being a
hooked function or something.

> +void __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...)
>  {
>         va_list args;
>         int len;
> @@ -53,8 +50,6 @@ void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...)
>         kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer);
>         kunit_kfree(current->kunit_test, buffer);
>  }
> -EXPORT_SYMBOL_GPL(__kunit_fail_current_test);
> -#endif
>
>  /*
>   * Enable KUnit tests to run.
> @@ -775,8 +770,18 @@ void kunit_cleanup(struct kunit *test)
>  }
>  EXPORT_SYMBOL_GPL(kunit_cleanup);
>
> +/* Declarations for the hook implemetnations */

nit: spelling

> +#define KUNIT_HOOK(name, retval, args) \
> +       extern retval name##_impl args
> +#include "kunit/hooks-table.h"
> +
>  static int __init kunit_init(void)
>  {
> +       /* Install the KUnit hook functions. */
> +#define KUNIT_HOOK(name, retval, args) \
> +       name = name##_impl
> +#include "kunit/hooks-table.h"
> +
>         kunit_debugfs_init();
>  #ifdef CONFIG_MODULES
>         return register_module_notifier(&kunit_mod_nb);
> @@ -788,6 +793,11 @@ late_initcall(kunit_init);
>
>  static void __exit kunit_exit(void)
>  {
> +       /* Remove the KUnit hook functions. */
> +#define KUNIT_HOOK(name, retval, args) \
> +       name = NULL
> +#include "kunit/hooks-table.h"
> +
>  #ifdef CONFIG_MODULES
>         unregister_module_notifier(&kunit_mod_nb);
>  #endif
> --
> 2.39.0.246.g2a6d74b583-goog

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
  2023-01-27  0:48 ` Brendan Higgins
@ 2023-01-27  1:31   ` David Gow
  0 siblings, 0 replies; 6+ messages in thread
From: David Gow @ 2023-01-27  1:31 UTC (permalink / raw)
  To: Brendan Higgins
  Cc: Shuah Khan, Brendan Higgins, Kees Cook, Daniel Latypov, Rae Moar,
	Sadiya Kazi, kunit-dev, linux-kselftest, linux-kernel

On Fri, 27 Jan 2023 at 08:49, Brendan Higgins <brendanhiggins@google.com> wrote:
>
> On Tue, Jan 24, 2023 at 3:04 AM 'David Gow' via KUnit Development
> <kunit-dev@googlegroups.com> wrote:
> >
> > KUnit has several macros and functions intended for use from non-test
> > code. These hooks, currently the kunit_get_current_test() and
> > kunit_fail_current_test() macros, didn't work when CONFIG_KUNIT=m.
> >
> > In order to support this case, the required functions and static data
> > need to be available unconditionally, even when KUnit itself is not
> > built-in. The new 'hooks.c' file is therefore always included, and has
> > both the static key required for kunit_get_current_test(), and a
> > function pointer to the real implementation of
> > __kunit_fail_current_test(), which is populated when the KUnit module is
> > loaded.
> >
> > A new header, kunit/hooks-table.h, contains a table of all hooks, and is
> > repeatedly included with different definitions of the KUNIT_HOOK() in
> > order to automatically generate the needed function pointer tables. When
> > KUnit is disabled, or the module is not loaded, these function pointers
> > are all NULL. This shouldn't be a problem, as they're all used behind
> > wrappers which check kunit_running and/or that the pointer is non-NULL.
> >
> > This can then be extended for future features which require similar
> > "hook" behaviour, such as static stubs:
> > https://lore.kernel.org/all/20221208061841.2186447-1-davidgow@google.com/
>
> Devilishly clever. Maybe too clever, but I don't have any better ideas, so:
>
> Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
>
> Nevertheless, see my comments below:
>

Thanks for checking this out! I definitely agree that this is verging
on over-complicated, but I do think it'll be worth it.

I'll send out a proper v1 in a day or two with your comments below
addressed (and a few other minor issues).
One option is to just include this in the same series as the static
stub feature (the next version of which will use it), but that may
just make things more confusing.

> > Signed-off-by: David Gow <davidgow@google.com>
> > ---
> >
> > This is basically a prerequisite for the stub features working when
> > KUnit is built as a module, and should nicely make a few other tests
> > work then, too.
> >
> > v2 adds a slightly-excessive macro-based system for defining hooks. This
> > made adding the static stub hooks absolutely trivial, and the complexity
> > is totally hidden from the user (being an internal KUnit implementation
> > detail), so I'm more comfortable with this than some other macro magic.
> >
> > It does however result in a huge number of checkpatch.pl errors, as
> > we're using macros in unconventional ways, and checkpatch just can't
> > work out the syntax. These are mostly "Macros with complex values should
> > be enclosed in parentheses", "Macros with multiple statements should be
> > enclosed in a do - while loop", and similar, which don't apply due to
> > the macros not being expressions: they are mostly declarations or
> > assignment statements. There are a few others where checkpatch thinks
> > that the return value is the function name and similar, so complains
> > about the style.
>
> Shuah, what are your thoughts here? I think it's OK, but I don't want
> to go any further down this path unless you are OK with it too.
>
> > Open questions:
> > - Is this macro-based system worth it, or was v1 better?
>
> I think this is definitely better if we had more than one function to
> hook. With just one function - I am less confident, but I like having
> a set way to do it.

Yeah, I agree that it's not worth it for just one function (hence the
first RFC just hardcoding these), but I think it pays of surprisingly
quickly as we add more.

In particular, the static stub code lives in a different file, so
would've needed an extra header for the "_impl" version anyway, as
it'd need to be accessible from the test.c init code which sets up the
function pointers. Once we're adding several headers, each with very
closely related definitions, this way already looks like a bit of a
win.

(And I have plans to add some more hooks going forward, particularly
for "data stubbing", as well as to expose some way of looking up
resources.)

>
> > - Should we rename test-bug.h to hooks.h or similar.
> >   (I think so, but would rather do it in a separate patch, to make it
> >   easier to review. There are a few includes of it scattered about.)
>
> Agreed, that confused me at first.

The other option we could do is to add "hooks.h" now, and temporarily
make "test-bug.h" just transitively include that until we clean up any
existing users.

>
> > - Is making these NULL when KUnit isn't around sensible, or should we
> >   auto-generate a "default" implementation. This is a pretty easy
> >   extension to the macros here.
> >   (I think NULL is good for now, as we have wrappers for these anyway.
> >   If we want to change our minds later as we add more hooks, it's easy.)
>
> Yeah, I'm fine with either.
>

I think we'll leave it as-is until we have enough hooks that it
justifies even more complexity.

> > - Any other thoughts?
>
> My primary concern was with the naming of test-bug.h, but you said
> you'd handle that in another patch, which makes sense to me.
>
> I also want to make sure Shuah is OK with the checkpatch warnings.
>

Yeah, the checkpatch warnings are the biggest worry I have. There are
good reasons to consider them false positives, but it still leaves a
bit of a bad taste in my mouth.

Here's the full list, with some notes:
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#116:
new file mode 100644

(The new files are in KUnit directories, so are already covered by MAINTAINERS)

ERROR: Macros with complex values should be enclosed in parentheses
#196: FILE: include/kunit/test-bug.h:57:
+#define KUNIT_HOOK(name, retval, args) \
+       extern retval (*name)args

(This is a global variable declaration, so can't be enclosed with
parentheses or do {} while (0))

WARNING: space prohibited between function name and open parenthesis '('
#197: FILE: include/kunit/test-bug.h:58:
+       extern retval (*name)args

(retval is not the function name, it's the return value. name is the
name, the parentheses are for the function pointer syntax.)

ERROR: Macros with complex values should be enclosed in parentheses
#212: FILE: include/kunit/test-bug.h:71:
+#define KUNIT_HOOK(name, retval, args) \
+       static retval (*name)args = NULL

(This is a global variable declaration, so can't be enclosed with
parentheses or do {} while (0))

WARNING: space prohibited between function name and open parenthesis '('
#213: FILE: include/kunit/test-bug.h:72:
+       static retval (*name)args = NULL

(retval is not the function name, it's the return value. name is the
name, the parentheses are for the function pointer syntax.)

WARNING: EXPORT_SYMBOL(foo); should immediately follow its function/variable
#271: FILE: lib/kunit/hooks.c:18:
+EXPORT_SYMBOL(kunit_running);

(This does immediately follow the DEFINE_STATIC_KEY macro)

ERROR: Macros with multiple statements should be enclosed in a do - while loop
#274: FILE: lib/kunit/hooks.c:21:
+#define KUNIT_HOOK(name, retval, args) \
+       retval (*name)args; \
+       EXPORT_SYMBOL(name)

(These are global declarations, so can't live in a do {} while (0) loop)

WARNING: space prohibited between function name and open parenthesis '('
#275: FILE: lib/kunit/hooks.c:22:
+       retval (*name)args; \

(retval is not the function name, it's the return value. name is the
name, the parentheses are for the function pointer syntax.)

ERROR: Macros with complex values should be enclosed in parentheses
#314: FILE: lib/kunit/test.c:774:
+#define KUNIT_HOOK(name, retval, args) \
+       extern retval name##_impl args

(This is a global variable declaration, so can't be enclosed with
parentheses or do {} while (0))

ERROR: Macros with complex values should be enclosed in parentheses
#321: FILE: lib/kunit/test.c:781:
+#define KUNIT_HOOK(name, retval, args) \
+       name = name##_impl

(I _guess_ this one and the following could be put in a do {} while
(0) loop, but parentheses wouldn't work, as this is an assignment.)

ERROR: Macros with complex values should be enclosed in parentheses
#333: FILE: lib/kunit/test.c:797:
+#define KUNIT_HOOK(name, retval, args) \
+       name = NULL

(I _guess_ this one and the previous could be put in a do {} while (0)
loop, but parentheses wouldn't work, as this is an assignment.)

total: 6 errors, 5 warnings, 211 lines checked


> I did find two nits below:
>
> > Cheers,
> > -- David
> >
> > Changes since RFC v1:
> > https://lore.kernel.org/all/20230117142737.246446-1-davidgow@google.com/
> > - Major refit to auto-generate the hook code using macros.
> > - (Note that previous Reviewed-by tags have not been added, as this is a
> >   big enough change it probably needs a re-reviews. Thanks Rae for
> >   reviewing RFC v1 previously, though!)
> > ---
> >  Documentation/dev-tools/kunit/usage.rst | 14 +++++-----
> >  include/kunit/hooks-table.h             | 34 +++++++++++++++++++++++++
> >  include/kunit/test-bug.h                | 24 +++++++++--------
> >  lib/Makefile                            |  4 +++
> >  lib/kunit/Makefile                      |  3 +++
> >  lib/kunit/hooks.c                       | 27 ++++++++++++++++++++
> >  lib/kunit/test.c                        | 22 +++++++++++-----
> >  7 files changed, 103 insertions(+), 25 deletions(-)
> >  create mode 100644 include/kunit/hooks-table.h
> >  create mode 100644 lib/kunit/hooks.c
> >
> > diff --git a/Documentation/dev-tools/kunit/usage.rst b/Documentation/dev-tools/kunit/usage.rst
> > index 48f8196d5aad..6424493b93cb 100644
> > --- a/Documentation/dev-tools/kunit/usage.rst
> > +++ b/Documentation/dev-tools/kunit/usage.rst
> > @@ -648,10 +648,9 @@ We can do this via the ``kunit_test`` field in ``task_struct``, which we can
> >  access using the ``kunit_get_current_test()`` function in ``kunit/test-bug.h``.
> >
> >  ``kunit_get_current_test()`` is safe to call even if KUnit is not enabled. If
> > -KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
> > -running in the current task, it will return ``NULL``. This compiles down to
> > -either a no-op or a static key check, so will have a negligible performance
> > -impact when no test is running.
> > +KUnit is not enabled, or if no test is running in the current task, it will
> > +return ``NULL``. This compiles down to either a no-op or a static key check,
> > +so will have a negligible performance impact when no test is running.
> >
> >  The example below uses this to implement a "mock" implementation of a function, ``foo``:
> >
> > @@ -726,8 +725,7 @@ structures as shown below:
> >         #endif
> >
> >  ``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
> > -KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
> > -running in the current task, it will do nothing. This compiles down to either a
> > -no-op or a static key check, so will have a negligible performance impact when
> > -no test is running.
> > +KUnit is not enabled, or if no test is running in the current task, it will do
> > +nothing. This compiles down to either a no-op or a static key check, so will
> > +have a negligible performance impact when no test is running.
> >
> > diff --git a/include/kunit/hooks-table.h b/include/kunit/hooks-table.h
> > new file mode 100644
> > index 000000000000..0b5eafd199ed
> > --- /dev/null
> > +++ b/include/kunit/hooks-table.h
> > @@ -0,0 +1,34 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * KUnit 'Hooks' function pointer table
> > + *
> > + * This file is included multiple times, each time with a different definition
> > + * of KUNIT_HOOK. This provides one place where all of the hooks can be listed
> > + * which can then be converted into function / implementation declarations, or
> > + * code to set function pointers.
> > + *
> > + * Copyright (C) 2023, Google LLC.
> > + * Author: David Gow <davidgow@google.com>
> > + */
> > +
> > +/*
> > + * To declare a hook, use:
> > + * KUNIT_HOOK(name, retval, args), where:
> > + * - name: the function name of the exported hook
> > + * - retval: the type of the return value of the hook
> > + * - args: the arguments to the hook, of the form (int a, int b)
> > + *
> > + * Note that the argument list should be contained within the brackets (),
> > + * and that the implementation of the hook should be in a <name>_impl
> > + * function, which should not be declared static, but need not be exported.
> > + */
> > +
> > +#ifndef KUNIT_HOOK
> > +#error KUNIT_HOOK must be defined before including the hooks table
> > +#endif
> > +
> > +KUNIT_HOOK(__kunit_fail_current_test, __printf(3, 4) void,
> > +          (const char *file, int line, const char *fmt, ...));
> > +
> > +/* Undefine KUNIT_HOOK at the end, ready for the next use. */
> > +#undef KUNIT_HOOK
> > diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
> > index c1b2e14eab64..3203ffc0a08b 100644
> > --- a/include/kunit/test-bug.h
> > +++ b/include/kunit/test-bug.h
> > @@ -1,6 +1,6 @@
> >  /* SPDX-License-Identifier: GPL-2.0 */
> >  /*
> > - * KUnit API allowing dynamic analysis tools to interact with KUnit tests
> > + * KUnit API providing hooks for non-test code to interact with tests.
> >   *
> >   * Copyright (C) 2020, Google LLC.
> >   * Author: Uriel Guajardo <urielguajardo@google.com>
> > @@ -9,7 +9,7 @@
> >  #ifndef _KUNIT_TEST_BUG_H
> >  #define _KUNIT_TEST_BUG_H
> >
> > -#if IS_BUILTIN(CONFIG_KUNIT)
> > +#if IS_ENABLED(CONFIG_KUNIT)
> >
> >  #include <linux/jump_label.h> /* For static branch */
> >  #include <linux/sched.h>
> > @@ -43,20 +43,21 @@ static inline struct kunit *kunit_get_current_test(void)
> >   * kunit_fail_current_test() - If a KUnit test is running, fail it.
> >   *
> >   * If a KUnit test is running in the current task, mark that test as failed.
> > - *
> > - * This macro will only work if KUnit is built-in (though the tests
> > - * themselves can be modules). Otherwise, it compiles down to nothing.
> >   */
> >  #define kunit_fail_current_test(fmt, ...) do {                                 \
> >                 if (static_branch_unlikely(&kunit_running)) {                   \
> > +                       /* Guaranteed to be non-NULL when kunit_running true*/  \
> >                         __kunit_fail_current_test(__FILE__, __LINE__,           \
> >                                                   fmt, ##__VA_ARGS__);          \
> >                 }                                                               \
> >         } while (0)
> >
> >
> > -extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
> > -                                                   const char *fmt, ...);
> > +/* Declare all of the available hooks. */
> > +#define KUNIT_HOOK(name, retval, args) \
> > +       extern retval (*name)args
> > +
> > +#include "kunit/hooks-table.h"
> >
> >  #else
> >
> > @@ -66,10 +67,11 @@ static inline struct kunit *kunit_get_current_test(void) { return NULL; }
> >  #define kunit_fail_current_test(fmt, ...) \
> >                 __kunit_fail_current_test(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
> >
> > -static inline __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
> > -                                                           const char *fmt, ...)
> > -{
> > -}
> > +/* No-op stubs if KUnit is not enabled. */
> > +#define KUNIT_HOOK(name, retval, args) \
> > +       static retval (*name)args = NULL
> > +
> > +#include "kunit/hooks-table.h"
> >
> >  #endif
> >
> > diff --git a/lib/Makefile b/lib/Makefile
> > index 4d9461bfea42..9031de6ca73c 100644
> > --- a/lib/Makefile
> > +++ b/lib/Makefile
> > @@ -126,6 +126,10 @@ CFLAGS_test_fpu.o += $(FPU_CFLAGS)
> >  obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
> >
> >  obj-$(CONFIG_KUNIT) += kunit/
> > +# Include the KUnit hooks unconditionally. They'll compile to nothing if
> > +# CONFIG_KUNIT=n, otherwise will be a small table of static data (static key,
> > +# function pointers) which need to be built-in even when KUnit is a module.
> > +obj-y += kunit/hooks.o
> >
> >  ifeq ($(CONFIG_DEBUG_KOBJECT),y)
> >  CFLAGS_kobject.o += -DDEBUG
> > diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> > index 29aff6562b42..deeb46cc879b 100644
> > --- a/lib/kunit/Makefile
> > +++ b/lib/kunit/Makefile
> > @@ -11,6 +11,9 @@ ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
> >  kunit-objs +=                          debugfs.o
> >  endif
> >
> > +# KUnit 'hooks' are built-in even when KUnit is built as a module.
> > +lib-y +=                               hooks.o
> > +
> >  obj-$(CONFIG_KUNIT_TEST) +=            kunit-test.o
> >
> >  # string-stream-test compiles built-in only.
> > diff --git a/lib/kunit/hooks.c b/lib/kunit/hooks.c
> > new file mode 100644
> > index 000000000000..29e81614f486
> > --- /dev/null
> > +++ b/lib/kunit/hooks.c
> > @@ -0,0 +1,27 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * KUnit 'Hooks' implementation.
> > + *
> > + * This file contains code / structures which should be built-in even when
> > + * KUnit itself is built as a module.
> > + *
> > + * Copyright (C) 2022, Google LLC.
> > + * Author: David Gow <davidgow@google.com>
> > + */
> > +
> > +/* This file is always built-in, so make sure it's empty if CONFIG_KUNIT=n */
> > +#if IS_ENABLED(CONFIG_KUNIT)
> > +
> > +#include <kunit/test-bug.h>
> > +
> > +DEFINE_STATIC_KEY_FALSE(kunit_running);
> > +EXPORT_SYMBOL(kunit_running);
> > +
> > +/* Function pointers for hooks. */
> > +#define KUNIT_HOOK(name, retval, args) \
> > +       retval (*name)args; \
> > +       EXPORT_SYMBOL(name)
> > +
> > +#include "kunit/hooks-table.h"
> > +
> > +#endif
> > diff --git a/lib/kunit/test.c b/lib/kunit/test.c
> > index c9ebf975e56b..b6c88f722b68 100644
> > --- a/lib/kunit/test.c
> > +++ b/lib/kunit/test.c
> > @@ -20,13 +20,10 @@
> >  #include "string-stream.h"
> >  #include "try-catch-impl.h"
> >
> > -DEFINE_STATIC_KEY_FALSE(kunit_running);
> > -
> > -#if IS_BUILTIN(CONFIG_KUNIT)
> >  /*
> >   * Fail the current test and print an error message to the log.
> >   */
> > -void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...)
>
> nit: I think it would be good to add a comment here about this being a
> hooked function or something.

Makes sense, will do.

>
> > +void __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...)
> >  {
> >         va_list args;
> >         int len;
> > @@ -53,8 +50,6 @@ void __kunit_fail_current_test(const char *file, int line, const char *fmt, ...)
> >         kunit_err(current->kunit_test, "%s:%d: %s", file, line, buffer);
> >         kunit_kfree(current->kunit_test, buffer);
> >  }
> > -EXPORT_SYMBOL_GPL(__kunit_fail_current_test);
> > -#endif
> >
> >  /*
> >   * Enable KUnit tests to run.
> > @@ -775,8 +770,18 @@ void kunit_cleanup(struct kunit *test)
> >  }
> >  EXPORT_SYMBOL_GPL(kunit_cleanup);
> >
> > +/* Declarations for the hook implemetnations */
>
> nit: spelling
>

Nice catch, thanks!

> > +#define KUNIT_HOOK(name, retval, args) \
> > +       extern retval name##_impl args
> > +#include "kunit/hooks-table.h"
> > +
> >  static int __init kunit_init(void)
> >  {
> > +       /* Install the KUnit hook functions. */
> > +#define KUNIT_HOOK(name, retval, args) \
> > +       name = name##_impl
> > +#include "kunit/hooks-table.h"
> > +
> >         kunit_debugfs_init();
> >  #ifdef CONFIG_MODULES
> >         return register_module_notifier(&kunit_mod_nb);
> > @@ -788,6 +793,11 @@ late_initcall(kunit_init);
> >
> >  static void __exit kunit_exit(void)
> >  {
> > +       /* Remove the KUnit hook functions. */
> > +#define KUNIT_HOOK(name, retval, args) \
> > +       name = NULL
> > +#include "kunit/hooks-table.h"
> > +
> >  #ifdef CONFIG_MODULES
> >         unregister_module_notifier(&kunit_mod_nb);
> >  #endif
> > --
> > 2.39.0.246.g2a6d74b583-goog

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
  2023-01-24  8:03 [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module David Gow
  2023-01-24 10:43 ` kernel test robot
  2023-01-27  0:48 ` Brendan Higgins
@ 2023-01-27  5:38 ` Daniel Latypov
  2023-01-27  6:21   ` David Gow
  2 siblings, 1 reply; 6+ messages in thread
From: Daniel Latypov @ 2023-01-27  5:38 UTC (permalink / raw)
  To: David Gow
  Cc: Brendan Higgins, Kees Cook, Shuah Khan, Rae Moar, Sadiya Kazi,
	kunit-dev, linux-kselftest, linux-kernel

On Tue, Jan 24, 2023 at 12:04 AM David Gow <davidgow@google.com> wrote:
>
> KUnit has several macros and functions intended for use from non-test
> code. These hooks, currently the kunit_get_current_test() and
> kunit_fail_current_test() macros, didn't work when CONFIG_KUNIT=m.
>
> In order to support this case, the required functions and static data
> need to be available unconditionally, even when KUnit itself is not
> built-in. The new 'hooks.c' file is therefore always included, and has
> both the static key required for kunit_get_current_test(), and a
> function pointer to the real implementation of
> __kunit_fail_current_test(), which is populated when the KUnit module is
> loaded.
>
> A new header, kunit/hooks-table.h, contains a table of all hooks, and is
> repeatedly included with different definitions of the KUNIT_HOOK() in
> order to automatically generate the needed function pointer tables. When

Perhaps I'm overlooking something and this is a dumb question.

Is there a reason we can't go with a less-clever approach?
Like have a global struct?
We could memset it to 0 to clear it instead of defining a macro to set
individual variables to NULL?

i.e.

// hooks.h
extern struct kunit_hook_table {
        __printf(3, 4) void (*fail_current_test)(const char*, int,
const char*, ...);
} kunit_hooks;

//hooks.c
struct kunit_hook_table kunit_hooks;

// in test.c
// here all the functions should be in scope for us to use
static void kunit_set_hooks(void)
{
  kunit_hooks.fail_current_test = __kunit_fail_current_test;
  ...
}

 static int __init kunit_init(void)
 {
  ...
  kunit_set_hooks();
  ...
}

static void __exit kunit_exit(void)
{
  ...
  memset(&kunit_hooks, 0, sizeof(kunit_hooks));
}

> KUnit is disabled, or the module is not loaded, these function pointers
> are all NULL. This shouldn't be a problem, as they're all used behind
> wrappers which check kunit_running and/or that the pointer is non-NULL.
>
> This can then be extended for future features which require similar
> "hook" behaviour, such as static stubs:
> https://lore.kernel.org/all/20221208061841.2186447-1-davidgow@google.com/
>
> Signed-off-by: David Gow <davidgow@google.com>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module
  2023-01-27  5:38 ` Daniel Latypov
@ 2023-01-27  6:21   ` David Gow
  0 siblings, 0 replies; 6+ messages in thread
From: David Gow @ 2023-01-27  6:21 UTC (permalink / raw)
  To: Daniel Latypov
  Cc: Brendan Higgins, Kees Cook, Shuah Khan, Rae Moar, Sadiya Kazi,
	kunit-dev, linux-kselftest, linux-kernel

On Fri, 27 Jan 2023 at 13:38, 'Daniel Latypov' via KUnit Development
<kunit-dev@googlegroups.com> wrote:
>
> On Tue, Jan 24, 2023 at 12:04 AM David Gow <davidgow@google.com> wrote:
> >
> > KUnit has several macros and functions intended for use from non-test
> > code. These hooks, currently the kunit_get_current_test() and
> > kunit_fail_current_test() macros, didn't work when CONFIG_KUNIT=m.
> >
> > In order to support this case, the required functions and static data
> > need to be available unconditionally, even when KUnit itself is not
> > built-in. The new 'hooks.c' file is therefore always included, and has
> > both the static key required for kunit_get_current_test(), and a
> > function pointer to the real implementation of
> > __kunit_fail_current_test(), which is populated when the KUnit module is
> > loaded.
> >
> > A new header, kunit/hooks-table.h, contains a table of all hooks, and is
> > repeatedly included with different definitions of the KUNIT_HOOK() in
> > order to automatically generate the needed function pointer tables. When
>
> Perhaps I'm overlooking something and this is a dumb question.
>
> Is there a reason we can't go with a less-clever approach?
> Like have a global struct?
> We could memset it to 0 to clear it instead of defining a macro to set
> individual variables to NULL?
>

I didn't think of that: it'd definitely fix the need to have a macro
for resetting the pointers to NULL, as well as the definitions.

We'd still have the repetition of the function names in the
kunit_set_hooks function and the table definition. (As well as needing
all of the implementation functions around.)

Still, I think there's value in putting this in a struct, even if we
also use the macro magic for other things. If we, for instance,
instead of setting individual members of struct kunit_hook_table, we
re-initialised it in one go, that might give the compiler enough
context to warn about uninitialised values if we missed one.

The only downside of the struct is that it's slightly uglier if people
want to call the hook function pointer directly. This is not as likely
at the moment, as it could be NULL, but it'd be possible to extend the
macro to generate a stub implementation which just returned nothing.
(Still, it'd be equally possible to autogenerate a wrapper function
which checks kunit_running, so this isn't a dealbreaker.)

> i.e.
>
> // hooks.h
> extern struct kunit_hook_table {
>         __printf(3, 4) void (*fail_current_test)(const char*, int,
> const char*, ...);
> } kunit_hooks;
>
> //hooks.c
> struct kunit_hook_table kunit_hooks;
>
> // in test.c
> // here all the functions should be in scope for us to use

This is actually the bit which pushed me over the line and made me
write the macro-based version: if the hook implementations are not all
in test.c (and the static stub ones are in static_stub.c), we'd have
to declare the hook implementation function somewhere, either
introducing a new hooks-impl.h or having a bunch of function
declarations in test.c.

My thought was, if we were going to need an extra header with more
definitions, we might as well just have one, which would also stop us
from worrying about missing an assignment in one place or the other.

> static void kunit_set_hooks(void)
> {
>   kunit_hooks.fail_current_test = __kunit_fail_current_test;
>   ...
> }
>
>  static int __init kunit_init(void)
>  {
>   ...
>   kunit_set_hooks();
>   ...
> }
>
> static void __exit kunit_exit(void)
> {
>   ...
>   memset(&kunit_hooks, 0, sizeof(kunit_hooks));
> }
>

I'll give moving this to a struct a go, it should be an improvement
even if I still use the macros to generate the struct.

The real advantage of the macro is only having to add the new hook in
two or three places:
- The hooks-table.h entry
- The _impl function (which can be in any file)
- (Optionally) a wrapper so that people don't need to check for NULL /
kunit_running themselves.

Without it, they'll have to:
- Add an entry to the struct kunit_hooks_table
- Write the implementation function.
- Add a declaration for the _impl function in test.c, if the function
isn't defined there.
- Add an entry to kunit_set_hooks()
- (Optionally) a wrapper, etc.

That's potentially twice as many things to get right. Still, it's a
lot better with the struct than doing each function individually, so
it's a closer tradeoff.
Personally, I still feel the macro-based version will eventually be
useful, but it's probably 50/50 whether it's worth it for just two or
three hooks.

Worst-case, we can do just the manual struct-based version, and
replace it with the macro one later.

Thanks,
-- David

> > KUnit is disabled, or the module is not loaded, these function pointers
> > are all NULL. This shouldn't be a problem, as they're all used behind
> > wrappers which check kunit_running and/or that the pointer is non-NULL.
> >
> > This can then be extended for future features which require similar
> > "hook" behaviour, such as static stubs:
> > https://lore.kernel.org/all/20221208061841.2186447-1-davidgow@google.com/
> >
> > Signed-off-by: David Gow <davidgow@google.com>
>
> --
> You received this message because you are subscribed to the Google Groups "KUnit Development" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kunit-dev+unsubscribe@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kunit-dev/CAGS_qxq4vWvRJ89477S%2BrxHYLvnc2xN435GQ4%2BBvpLgqon8miw%40mail.gmail.com.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2023-01-27  6:22 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-24  8:03 [RFC PATCH v2] kunit: Add "hooks" to call into KUnit when it's built as a module David Gow
2023-01-24 10:43 ` kernel test robot
2023-01-27  0:48 ` Brendan Higgins
2023-01-27  1:31   ` David Gow
2023-01-27  5:38 ` Daniel Latypov
2023-01-27  6:21   ` David Gow

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.