Skip to content

Commit 89f1a24

Browse files
committed
Merge tag 'linux-kselftest-kunit-6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest
Pull KUnit update from Shuah Khan: - add Function Redirection API to isolate the code being tested from other parts of the kernel. Documentation/dev-tools/kunit/api/functionredirection.rst has the details. * tag 'linux-kselftest-kunit-6.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: kunit: Add printf attribute to fail_current_test_impl lib/hashtable_test.c: add test for the hashtable structure Documentation: Add Function Redirection API docs kunit: Expose 'static stub' API to redirect functions kunit: Add "hooks" to call into KUnit when it's built as a module kunit: kunit.py extract handlers tools/testing/kunit/kunit.py: remove redundant double check
2 parents d6296cb + 82649c7 commit 89f1a24

File tree

15 files changed

+966
-123
lines changed

15 files changed

+966
-123
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
.. SPDX-License-Identifier: GPL-2.0
2+
3+
========================
4+
Function Redirection API
5+
========================
6+
7+
Overview
8+
========
9+
10+
When writing unit tests, it's important to be able to isolate the code being
11+
tested from other parts of the kernel. This ensures the reliability of the test
12+
(it won't be affected by external factors), reduces dependencies on specific
13+
hardware or config options (making the test easier to run), and protects the
14+
stability of the rest of the system (making it less likely for test-specific
15+
state to interfere with the rest of the system).
16+
17+
While for some code (typically generic data structures, helpers, and other
18+
"pure functions") this is trivial, for others (like device drivers,
19+
filesystems, core subsystems) the code is heavily coupled with other parts of
20+
the kernel.
21+
22+
This coupling is often due to global state in some way: be it a global list of
23+
devices, the filesystem, or some hardware state. Tests need to either carefully
24+
manage, isolate, and restore state, or they can avoid it altogether by
25+
replacing access to and mutation of this state with a "fake" or "mock" variant.
26+
27+
By refactoring access to such state, such as by introducing a layer of
28+
indirection which can use or emulate a separate set of test state. However,
29+
such refactoring comes with its own costs (and undertaking significant
30+
refactoring before being able to write tests is suboptimal).
31+
32+
A simpler way to intercept and replace some of the function calls is to use
33+
function redirection via static stubs.
34+
35+
36+
Static Stubs
37+
============
38+
39+
Static stubs are a way of redirecting calls to one function (the "real"
40+
function) to another function (the "replacement" function).
41+
42+
It works by adding a macro to the "real" function which checks to see if a test
43+
is running, and if a replacement function is available. If so, that function is
44+
called in place of the original.
45+
46+
Using static stubs is pretty straightforward:
47+
48+
1. Add the KUNIT_STATIC_STUB_REDIRECT() macro to the start of the "real"
49+
function.
50+
51+
This should be the first statement in the function, after any variable
52+
declarations. KUNIT_STATIC_STUB_REDIRECT() takes the name of the
53+
function, followed by all of the arguments passed to the real function.
54+
55+
For example:
56+
57+
.. code-block:: c
58+
59+
void send_data_to_hardware(const char *str)
60+
{
61+
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
62+
/* real implementation */
63+
}
64+
65+
2. Write one or more replacement functions.
66+
67+
These functions should have the same function signature as the real function.
68+
In the event they need to access or modify test-specific state, they can use
69+
kunit_get_current_test() to get a struct kunit pointer. This can then
70+
be passed to the expectation/assertion macros, or used to look up KUnit
71+
resources.
72+
73+
For example:
74+
75+
.. code-block:: c
76+
77+
void fake_send_data_to_hardware(const char *str)
78+
{
79+
struct kunit *test = kunit_get_current_test();
80+
KUNIT_EXPECT_STREQ(test, str, "Hello World!");
81+
}
82+
83+
3. Activate the static stub from your test.
84+
85+
From within a test, the redirection can be enabled with
86+
kunit_activate_static_stub(), which accepts a struct kunit pointer,
87+
the real function, and the replacement function. You can call this several
88+
times with different replacement functions to swap out implementations of the
89+
function.
90+
91+
In our example, this would be
92+
93+
.. code-block:: c
94+
95+
kunit_activate_static_stub(test,
96+
send_data_to_hardware,
97+
fake_send_data_to_hardware);
98+
99+
4. Call (perhaps indirectly) the real function.
100+
101+
Once the redirection is activated, any call to the real function will call
102+
the replacement function instead. Such calls may be buried deep in the
103+
implementation of another function, but must occur from the test's kthread.
104+
105+
For example:
106+
107+
.. code-block:: c
108+
109+
send_data_to_hardware("Hello World!"); /* Succeeds */
110+
send_data_to_hardware("Something else"); /* Fails the test. */
111+
112+
5. (Optionally) disable the stub.
113+
114+
When you no longer need it, disable the redirection (and hence resume the
115+
original behaviour of the 'real' function) using
116+
kunit_deactivate_static_stub(). Otherwise, it will be automatically disabled
117+
when the test exits.
118+
119+
For example:
120+
121+
.. code-block:: c
122+
123+
kunit_deactivate_static_stub(test, send_data_to_hardware);
124+
125+
126+
It's also possible to use these replacement functions to test to see if a
127+
function is called at all, for example:
128+
129+
.. code-block:: c
130+
131+
void send_data_to_hardware(const char *str)
132+
{
133+
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
134+
/* real implementation */
135+
}
136+
137+
/* In test file */
138+
int times_called = 0;
139+
void fake_send_data_to_hardware(const char *str)
140+
{
141+
times_called++;
142+
}
143+
...
144+
/* In the test case, redirect calls for the duration of the test */
145+
kunit_activate_static_stub(test, send_data_to_hardware, fake_send_data_to_hardware);
146+
147+
send_data_to_hardware("hello");
148+
KUNIT_EXPECT_EQ(test, times_called, 1);
149+
150+
/* Can also deactivate the stub early, if wanted */
151+
kunit_deactivate_static_stub(test, send_data_to_hardware);
152+
153+
send_data_to_hardware("hello again");
154+
KUNIT_EXPECT_EQ(test, times_called, 1);
155+
156+
157+
158+
API Reference
159+
=============
160+
161+
.. kernel-doc:: include/kunit/static_stub.h
162+
:internal:

Documentation/dev-tools/kunit/api/index.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@
44
API Reference
55
=============
66
.. toctree::
7+
:hidden:
78

89
test
910
resource
11+
functionredirection
1012

11-
This section documents the KUnit kernel testing API. It is divided into the
13+
14+
This page documents the KUnit kernel testing API. It is divided into the
1215
following sections:
1316

1417
Documentation/dev-tools/kunit/api/test.rst
1518

16-
- documents all of the standard testing API
19+
- Documents all of the standard testing API
1720

1821
Documentation/dev-tools/kunit/api/resource.rst
1922

20-
- documents the KUnit resource API
23+
- Documents the KUnit resource API
24+
25+
Documentation/dev-tools/kunit/api/functionredirection.rst
26+
27+
- Documents the KUnit Function Redirection API

Documentation/dev-tools/kunit/usage.rst

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -648,10 +648,9 @@ We can do this via the ``kunit_test`` field in ``task_struct``, which we can
648648
access using the ``kunit_get_current_test()`` function in ``kunit/test-bug.h``.
649649

650650
``kunit_get_current_test()`` is safe to call even if KUnit is not enabled. If
651-
KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
652-
running in the current task, it will return ``NULL``. This compiles down to
653-
either a no-op or a static key check, so will have a negligible performance
654-
impact when no test is running.
651+
KUnit is not enabled, or if no test is running in the current task, it will
652+
return ``NULL``. This compiles down to either a no-op or a static key check,
653+
so will have a negligible performance impact when no test is running.
655654

656655
The example below uses this to implement a "mock" implementation of a function, ``foo``:
657656

@@ -726,8 +725,6 @@ structures as shown below:
726725
#endif
727726
728727
``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
729-
KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
730-
running in the current task, it will do nothing. This compiles down to either a
731-
no-op or a static key check, so will have a negligible performance impact when
732-
no test is running.
733-
728+
KUnit is not enabled, or if no test is running in the current task, it will do
729+
nothing. This compiles down to either a no-op or a static key check, so will
730+
have a negligible performance impact when no test is running.

include/kunit/static_stub.h

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* KUnit function redirection (static stubbing) API.
4+
*
5+
* Copyright (C) 2022, Google LLC.
6+
* Author: David Gow <[email protected]>
7+
*/
8+
#ifndef _KUNIT_STATIC_STUB_H
9+
#define _KUNIT_STATIC_STUB_H
10+
11+
#if !IS_ENABLED(CONFIG_KUNIT)
12+
13+
/* If CONFIG_KUNIT is not enabled, these stubs quietly disappear. */
14+
#define KUNIT_TRIGGER_STATIC_STUB(real_fn_name, args...) do {} while (0)
15+
16+
#else
17+
18+
#include <kunit/test.h>
19+
#include <kunit/test-bug.h>
20+
21+
#include <linux/compiler.h> /* for {un,}likely() */
22+
#include <linux/sched.h> /* for task_struct */
23+
24+
25+
/**
26+
* KUNIT_STATIC_STUB_REDIRECT() - call a replacement 'static stub' if one exists
27+
* @real_fn_name: The name of this function (as an identifier, not a string)
28+
* @args: All of the arguments passed to this function
29+
*
30+
* This is a function prologue which is used to allow calls to the current
31+
* function to be redirected by a KUnit test. KUnit tests can call
32+
* kunit_activate_static_stub() to pass a replacement function in. The
33+
* replacement function will be called by KUNIT_TRIGGER_STATIC_STUB(), which
34+
* will then return from the function. If the caller is not in a KUnit context,
35+
* the function will continue execution as normal.
36+
*
37+
* Example:
38+
*
39+
* .. code-block:: c
40+
*
41+
* int real_func(int n)
42+
* {
43+
* KUNIT_STATIC_STUB_REDIRECT(real_func, n);
44+
* return 0;
45+
* }
46+
*
47+
* int replacement_func(int n)
48+
* {
49+
* return 42;
50+
* }
51+
*
52+
* void example_test(struct kunit *test)
53+
* {
54+
* kunit_activate_static_stub(test, real_func, replacement_func);
55+
* KUNIT_EXPECT_EQ(test, real_func(1), 42);
56+
* }
57+
*
58+
*/
59+
#define KUNIT_STATIC_STUB_REDIRECT(real_fn_name, args...) \
60+
do { \
61+
typeof(&real_fn_name) replacement; \
62+
struct kunit *current_test = kunit_get_current_test(); \
63+
\
64+
if (likely(!current_test)) \
65+
break; \
66+
\
67+
replacement = kunit_hooks.get_static_stub_address(current_test, \
68+
&real_fn_name); \
69+
\
70+
if (unlikely(replacement)) \
71+
return replacement(args); \
72+
} while (0)
73+
74+
/* Helper function for kunit_activate_static_stub(). The macro does
75+
* typechecking, so use it instead.
76+
*/
77+
void __kunit_activate_static_stub(struct kunit *test,
78+
void *real_fn_addr,
79+
void *replacement_addr);
80+
81+
/**
82+
* kunit_activate_static_stub() - replace a function using static stubs.
83+
* @test: A pointer to the 'struct kunit' test context for the current test.
84+
* @real_fn_addr: The address of the function to replace.
85+
* @replacement_addr: The address of the function to replace it with.
86+
*
87+
* When activated, calls to real_fn_addr from within this test (even if called
88+
* indirectly) will instead call replacement_addr. The function pointed to by
89+
* real_fn_addr must begin with the static stub prologue in
90+
* KUNIT_TRIGGER_STATIC_STUB() for this to work. real_fn_addr and
91+
* replacement_addr must have the same type.
92+
*
93+
* The redirection can be disabled again with kunit_deactivate_static_stub().
94+
*/
95+
#define kunit_activate_static_stub(test, real_fn_addr, replacement_addr) do { \
96+
typecheck_fn(typeof(&real_fn_addr), replacement_addr); \
97+
__kunit_activate_static_stub(test, real_fn_addr, replacement_addr); \
98+
} while (0)
99+
100+
101+
/**
102+
* kunit_deactivate_static_stub() - disable a function redirection
103+
* @test: A pointer to the 'struct kunit' test context for the current test.
104+
* @real_fn_addr: The address of the function to no-longer redirect
105+
*
106+
* Deactivates a redirection configured with kunit_activate_static_stub(). After
107+
* this function returns, calls to real_fn_addr() will execute the original
108+
* real_fn, not any previously-configured replacement.
109+
*/
110+
void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr);
111+
112+
#endif
113+
#endif

include/kunit/test-bug.h

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* SPDX-License-Identifier: GPL-2.0 */
22
/*
3-
* KUnit API allowing dynamic analysis tools to interact with KUnit tests
3+
* KUnit API providing hooks for non-test code to interact with tests.
44
*
55
* Copyright (C) 2020, Google LLC.
66
* Author: Uriel Guajardo <[email protected]>
@@ -9,14 +9,20 @@
99
#ifndef _KUNIT_TEST_BUG_H
1010
#define _KUNIT_TEST_BUG_H
1111

12-
#if IS_BUILTIN(CONFIG_KUNIT)
12+
#if IS_ENABLED(CONFIG_KUNIT)
1313

1414
#include <linux/jump_label.h> /* For static branch */
1515
#include <linux/sched.h>
1616

1717
/* Static key if KUnit is running any tests. */
1818
DECLARE_STATIC_KEY_FALSE(kunit_running);
1919

20+
/* Hooks table: a table of function pointers filled in when kunit loads */
21+
extern struct kunit_hooks_table {
22+
__printf(3, 4) void (*fail_current_test)(const char*, int, const char*, ...);
23+
void *(*get_static_stub_address)(struct kunit *test, void *real_fn_addr);
24+
} kunit_hooks;
25+
2026
/**
2127
* kunit_get_current_test() - Return a pointer to the currently running
2228
* KUnit test.
@@ -43,33 +49,20 @@ static inline struct kunit *kunit_get_current_test(void)
4349
* kunit_fail_current_test() - If a KUnit test is running, fail it.
4450
*
4551
* If a KUnit test is running in the current task, mark that test as failed.
46-
*
47-
* This macro will only work if KUnit is built-in (though the tests
48-
* themselves can be modules). Otherwise, it compiles down to nothing.
4952
*/
5053
#define kunit_fail_current_test(fmt, ...) do { \
5154
if (static_branch_unlikely(&kunit_running)) { \
52-
__kunit_fail_current_test(__FILE__, __LINE__, \
55+
/* Guaranteed to be non-NULL when kunit_running true*/ \
56+
kunit_hooks.fail_current_test(__FILE__, __LINE__, \
5357
fmt, ##__VA_ARGS__); \
5458
} \
5559
} while (0)
5660

57-
58-
extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
59-
const char *fmt, ...);
60-
6161
#else
6262

6363
static inline struct kunit *kunit_get_current_test(void) { return NULL; }
6464

65-
/* We define this with an empty helper function so format string warnings work */
66-
#define kunit_fail_current_test(fmt, ...) \
67-
__kunit_fail_current_test(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
68-
69-
static inline __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
70-
const char *fmt, ...)
71-
{
72-
}
65+
#define kunit_fail_current_test(fmt, ...) do {} while (0)
7366

7467
#endif
7568

0 commit comments

Comments
 (0)