mirror of
https://github.com/tbsdtv/linux_media.git
synced 2025-07-23 12:43:29 +02:00
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
This commit is contained in:
162
Documentation/dev-tools/kunit/api/functionredirection.rst
Normal file
162
Documentation/dev-tools/kunit/api/functionredirection.rst
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
.. SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
========================
|
||||||
|
Function Redirection API
|
||||||
|
========================
|
||||||
|
|
||||||
|
Overview
|
||||||
|
========
|
||||||
|
|
||||||
|
When writing unit tests, it's important to be able to isolate the code being
|
||||||
|
tested from other parts of the kernel. This ensures the reliability of the test
|
||||||
|
(it won't be affected by external factors), reduces dependencies on specific
|
||||||
|
hardware or config options (making the test easier to run), and protects the
|
||||||
|
stability of the rest of the system (making it less likely for test-specific
|
||||||
|
state to interfere with the rest of the system).
|
||||||
|
|
||||||
|
While for some code (typically generic data structures, helpers, and other
|
||||||
|
"pure functions") this is trivial, for others (like device drivers,
|
||||||
|
filesystems, core subsystems) the code is heavily coupled with other parts of
|
||||||
|
the kernel.
|
||||||
|
|
||||||
|
This coupling is often due to global state in some way: be it a global list of
|
||||||
|
devices, the filesystem, or some hardware state. Tests need to either carefully
|
||||||
|
manage, isolate, and restore state, or they can avoid it altogether by
|
||||||
|
replacing access to and mutation of this state with a "fake" or "mock" variant.
|
||||||
|
|
||||||
|
By refactoring access to such state, such as by introducing a layer of
|
||||||
|
indirection which can use or emulate a separate set of test state. However,
|
||||||
|
such refactoring comes with its own costs (and undertaking significant
|
||||||
|
refactoring before being able to write tests is suboptimal).
|
||||||
|
|
||||||
|
A simpler way to intercept and replace some of the function calls is to use
|
||||||
|
function redirection via static stubs.
|
||||||
|
|
||||||
|
|
||||||
|
Static Stubs
|
||||||
|
============
|
||||||
|
|
||||||
|
Static stubs are a way of redirecting calls to one function (the "real"
|
||||||
|
function) to another function (the "replacement" function).
|
||||||
|
|
||||||
|
It works by adding a macro to the "real" function which checks to see if a test
|
||||||
|
is running, and if a replacement function is available. If so, that function is
|
||||||
|
called in place of the original.
|
||||||
|
|
||||||
|
Using static stubs is pretty straightforward:
|
||||||
|
|
||||||
|
1. Add the KUNIT_STATIC_STUB_REDIRECT() macro to the start of the "real"
|
||||||
|
function.
|
||||||
|
|
||||||
|
This should be the first statement in the function, after any variable
|
||||||
|
declarations. KUNIT_STATIC_STUB_REDIRECT() takes the name of the
|
||||||
|
function, followed by all of the arguments passed to the real function.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
void send_data_to_hardware(const char *str)
|
||||||
|
{
|
||||||
|
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
|
||||||
|
/* real implementation */
|
||||||
|
}
|
||||||
|
|
||||||
|
2. Write one or more replacement functions.
|
||||||
|
|
||||||
|
These functions should have the same function signature as the real function.
|
||||||
|
In the event they need to access or modify test-specific state, they can use
|
||||||
|
kunit_get_current_test() to get a struct kunit pointer. This can then
|
||||||
|
be passed to the expectation/assertion macros, or used to look up KUnit
|
||||||
|
resources.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
void fake_send_data_to_hardware(const char *str)
|
||||||
|
{
|
||||||
|
struct kunit *test = kunit_get_current_test();
|
||||||
|
KUNIT_EXPECT_STREQ(test, str, "Hello World!");
|
||||||
|
}
|
||||||
|
|
||||||
|
3. Activate the static stub from your test.
|
||||||
|
|
||||||
|
From within a test, the redirection can be enabled with
|
||||||
|
kunit_activate_static_stub(), which accepts a struct kunit pointer,
|
||||||
|
the real function, and the replacement function. You can call this several
|
||||||
|
times with different replacement functions to swap out implementations of the
|
||||||
|
function.
|
||||||
|
|
||||||
|
In our example, this would be
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
kunit_activate_static_stub(test,
|
||||||
|
send_data_to_hardware,
|
||||||
|
fake_send_data_to_hardware);
|
||||||
|
|
||||||
|
4. Call (perhaps indirectly) the real function.
|
||||||
|
|
||||||
|
Once the redirection is activated, any call to the real function will call
|
||||||
|
the replacement function instead. Such calls may be buried deep in the
|
||||||
|
implementation of another function, but must occur from the test's kthread.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
send_data_to_hardware("Hello World!"); /* Succeeds */
|
||||||
|
send_data_to_hardware("Something else"); /* Fails the test. */
|
||||||
|
|
||||||
|
5. (Optionally) disable the stub.
|
||||||
|
|
||||||
|
When you no longer need it, disable the redirection (and hence resume the
|
||||||
|
original behaviour of the 'real' function) using
|
||||||
|
kunit_deactivate_static_stub(). Otherwise, it will be automatically disabled
|
||||||
|
when the test exits.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
kunit_deactivate_static_stub(test, send_data_to_hardware);
|
||||||
|
|
||||||
|
|
||||||
|
It's also possible to use these replacement functions to test to see if a
|
||||||
|
function is called at all, for example:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
|
void send_data_to_hardware(const char *str)
|
||||||
|
{
|
||||||
|
KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
|
||||||
|
/* real implementation */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In test file */
|
||||||
|
int times_called = 0;
|
||||||
|
void fake_send_data_to_hardware(const char *str)
|
||||||
|
{
|
||||||
|
times_called++;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
/* In the test case, redirect calls for the duration of the test */
|
||||||
|
kunit_activate_static_stub(test, send_data_to_hardware, fake_send_data_to_hardware);
|
||||||
|
|
||||||
|
send_data_to_hardware("hello");
|
||||||
|
KUNIT_EXPECT_EQ(test, times_called, 1);
|
||||||
|
|
||||||
|
/* Can also deactivate the stub early, if wanted */
|
||||||
|
kunit_deactivate_static_stub(test, send_data_to_hardware);
|
||||||
|
|
||||||
|
send_data_to_hardware("hello again");
|
||||||
|
KUNIT_EXPECT_EQ(test, times_called, 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. kernel-doc:: include/kunit/static_stub.h
|
||||||
|
:internal:
|
@@ -4,17 +4,24 @@
|
|||||||
API Reference
|
API Reference
|
||||||
=============
|
=============
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
:hidden:
|
||||||
|
|
||||||
test
|
test
|
||||||
resource
|
resource
|
||||||
|
functionredirection
|
||||||
|
|
||||||
This section documents the KUnit kernel testing API. It is divided into the
|
|
||||||
|
This page documents the KUnit kernel testing API. It is divided into the
|
||||||
following sections:
|
following sections:
|
||||||
|
|
||||||
Documentation/dev-tools/kunit/api/test.rst
|
Documentation/dev-tools/kunit/api/test.rst
|
||||||
|
|
||||||
- documents all of the standard testing API
|
- Documents all of the standard testing API
|
||||||
|
|
||||||
Documentation/dev-tools/kunit/api/resource.rst
|
Documentation/dev-tools/kunit/api/resource.rst
|
||||||
|
|
||||||
- documents the KUnit resource API
|
- Documents the KUnit resource API
|
||||||
|
|
||||||
|
Documentation/dev-tools/kunit/api/functionredirection.rst
|
||||||
|
|
||||||
|
- Documents the KUnit Function Redirection API
|
||||||
|
@@ -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``.
|
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_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
|
KUnit is not enabled, or if no test is running in the current task, it will
|
||||||
running in the current task, it will return ``NULL``. This compiles down to
|
return ``NULL``. This compiles down to either a no-op or a static key check,
|
||||||
either a no-op or a static key check, so will have a negligible performance
|
so will have a negligible performance impact when no test is running.
|
||||||
impact when no test is running.
|
|
||||||
|
|
||||||
The example below uses this to implement a "mock" implementation of a function, ``foo``:
|
The example below uses this to implement a "mock" implementation of a function, ``foo``:
|
||||||
|
|
||||||
@@ -726,8 +725,6 @@ structures as shown below:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
|
``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
|
KUnit is not enabled, or if no test is running in the current task, it will do
|
||||||
running in the current task, it will do nothing. This compiles down to either a
|
nothing. This compiles down to either a no-op or a static key check, so will
|
||||||
no-op or a static key check, so will have a negligible performance impact when
|
have a negligible performance impact when no test is running.
|
||||||
no test is running.
|
|
||||||
|
|
||||||
|
113
include/kunit/static_stub.h
Normal file
113
include/kunit/static_stub.h
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* KUnit function redirection (static stubbing) API.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022, Google LLC.
|
||||||
|
* Author: David Gow <davidgow@google.com>
|
||||||
|
*/
|
||||||
|
#ifndef _KUNIT_STATIC_STUB_H
|
||||||
|
#define _KUNIT_STATIC_STUB_H
|
||||||
|
|
||||||
|
#if !IS_ENABLED(CONFIG_KUNIT)
|
||||||
|
|
||||||
|
/* If CONFIG_KUNIT is not enabled, these stubs quietly disappear. */
|
||||||
|
#define KUNIT_TRIGGER_STATIC_STUB(real_fn_name, args...) do {} while (0)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <kunit/test.h>
|
||||||
|
#include <kunit/test-bug.h>
|
||||||
|
|
||||||
|
#include <linux/compiler.h> /* for {un,}likely() */
|
||||||
|
#include <linux/sched.h> /* for task_struct */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KUNIT_STATIC_STUB_REDIRECT() - call a replacement 'static stub' if one exists
|
||||||
|
* @real_fn_name: The name of this function (as an identifier, not a string)
|
||||||
|
* @args: All of the arguments passed to this function
|
||||||
|
*
|
||||||
|
* This is a function prologue which is used to allow calls to the current
|
||||||
|
* function to be redirected by a KUnit test. KUnit tests can call
|
||||||
|
* kunit_activate_static_stub() to pass a replacement function in. The
|
||||||
|
* replacement function will be called by KUNIT_TRIGGER_STATIC_STUB(), which
|
||||||
|
* will then return from the function. If the caller is not in a KUnit context,
|
||||||
|
* the function will continue execution as normal.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* .. code-block:: c
|
||||||
|
*
|
||||||
|
* int real_func(int n)
|
||||||
|
* {
|
||||||
|
* KUNIT_STATIC_STUB_REDIRECT(real_func, n);
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* int replacement_func(int n)
|
||||||
|
* {
|
||||||
|
* return 42;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* void example_test(struct kunit *test)
|
||||||
|
* {
|
||||||
|
* kunit_activate_static_stub(test, real_func, replacement_func);
|
||||||
|
* KUNIT_EXPECT_EQ(test, real_func(1), 42);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define KUNIT_STATIC_STUB_REDIRECT(real_fn_name, args...) \
|
||||||
|
do { \
|
||||||
|
typeof(&real_fn_name) replacement; \
|
||||||
|
struct kunit *current_test = kunit_get_current_test(); \
|
||||||
|
\
|
||||||
|
if (likely(!current_test)) \
|
||||||
|
break; \
|
||||||
|
\
|
||||||
|
replacement = kunit_hooks.get_static_stub_address(current_test, \
|
||||||
|
&real_fn_name); \
|
||||||
|
\
|
||||||
|
if (unlikely(replacement)) \
|
||||||
|
return replacement(args); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Helper function for kunit_activate_static_stub(). The macro does
|
||||||
|
* typechecking, so use it instead.
|
||||||
|
*/
|
||||||
|
void __kunit_activate_static_stub(struct kunit *test,
|
||||||
|
void *real_fn_addr,
|
||||||
|
void *replacement_addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kunit_activate_static_stub() - replace a function using static stubs.
|
||||||
|
* @test: A pointer to the 'struct kunit' test context for the current test.
|
||||||
|
* @real_fn_addr: The address of the function to replace.
|
||||||
|
* @replacement_addr: The address of the function to replace it with.
|
||||||
|
*
|
||||||
|
* When activated, calls to real_fn_addr from within this test (even if called
|
||||||
|
* indirectly) will instead call replacement_addr. The function pointed to by
|
||||||
|
* real_fn_addr must begin with the static stub prologue in
|
||||||
|
* KUNIT_TRIGGER_STATIC_STUB() for this to work. real_fn_addr and
|
||||||
|
* replacement_addr must have the same type.
|
||||||
|
*
|
||||||
|
* The redirection can be disabled again with kunit_deactivate_static_stub().
|
||||||
|
*/
|
||||||
|
#define kunit_activate_static_stub(test, real_fn_addr, replacement_addr) do { \
|
||||||
|
typecheck_fn(typeof(&real_fn_addr), replacement_addr); \
|
||||||
|
__kunit_activate_static_stub(test, real_fn_addr, replacement_addr); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* kunit_deactivate_static_stub() - disable a function redirection
|
||||||
|
* @test: A pointer to the 'struct kunit' test context for the current test.
|
||||||
|
* @real_fn_addr: The address of the function to no-longer redirect
|
||||||
|
*
|
||||||
|
* Deactivates a redirection configured with kunit_activate_static_stub(). After
|
||||||
|
* this function returns, calls to real_fn_addr() will execute the original
|
||||||
|
* real_fn, not any previously-configured replacement.
|
||||||
|
*/
|
||||||
|
void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0 */
|
/* 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.
|
* Copyright (C) 2020, Google LLC.
|
||||||
* Author: Uriel Guajardo <urielguajardo@google.com>
|
* Author: Uriel Guajardo <urielguajardo@google.com>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
#ifndef _KUNIT_TEST_BUG_H
|
#ifndef _KUNIT_TEST_BUG_H
|
||||||
#define _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/jump_label.h> /* For static branch */
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
@@ -17,6 +17,12 @@
|
|||||||
/* Static key if KUnit is running any tests. */
|
/* Static key if KUnit is running any tests. */
|
||||||
DECLARE_STATIC_KEY_FALSE(kunit_running);
|
DECLARE_STATIC_KEY_FALSE(kunit_running);
|
||||||
|
|
||||||
|
/* Hooks table: a table of function pointers filled in when kunit loads */
|
||||||
|
extern struct kunit_hooks_table {
|
||||||
|
__printf(3, 4) void (*fail_current_test)(const char*, int, const char*, ...);
|
||||||
|
void *(*get_static_stub_address)(struct kunit *test, void *real_fn_addr);
|
||||||
|
} kunit_hooks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kunit_get_current_test() - Return a pointer to the currently running
|
* kunit_get_current_test() - Return a pointer to the currently running
|
||||||
* KUnit test.
|
* KUnit test.
|
||||||
@@ -43,33 +49,20 @@ static inline struct kunit *kunit_get_current_test(void)
|
|||||||
* kunit_fail_current_test() - If a KUnit test is running, fail it.
|
* 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.
|
* 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 { \
|
#define kunit_fail_current_test(fmt, ...) do { \
|
||||||
if (static_branch_unlikely(&kunit_running)) { \
|
if (static_branch_unlikely(&kunit_running)) { \
|
||||||
__kunit_fail_current_test(__FILE__, __LINE__, \
|
/* Guaranteed to be non-NULL when kunit_running true*/ \
|
||||||
|
kunit_hooks.fail_current_test(__FILE__, __LINE__, \
|
||||||
fmt, ##__VA_ARGS__); \
|
fmt, ##__VA_ARGS__); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
|
|
||||||
const char *fmt, ...);
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static inline struct kunit *kunit_get_current_test(void) { return NULL; }
|
static inline struct kunit *kunit_get_current_test(void) { return NULL; }
|
||||||
|
|
||||||
/* We define this with an empty helper function so format string warnings work */
|
#define kunit_fail_current_test(fmt, ...) do {} while (0)
|
||||||
#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, ...)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -2517,6 +2517,19 @@ config LIST_KUNIT_TEST
|
|||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config HASHTABLE_KUNIT_TEST
|
||||||
|
tristate "KUnit Test for Kernel Hashtable structures" if !KUNIT_ALL_TESTS
|
||||||
|
depends on KUNIT
|
||||||
|
default KUNIT_ALL_TESTS
|
||||||
|
help
|
||||||
|
This builds the hashtable KUnit test suite.
|
||||||
|
It tests the basic functionality of the API defined in
|
||||||
|
include/linux/hashtable.h. 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 LINEAR_RANGES_TEST
|
config LINEAR_RANGES_TEST
|
||||||
tristate "KUnit test for linear_ranges"
|
tristate "KUnit test for linear_ranges"
|
||||||
depends on KUNIT
|
depends on KUNIT
|
||||||
|
@@ -126,6 +126,14 @@ CFLAGS_test_fpu.o += $(FPU_CFLAGS)
|
|||||||
obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
|
obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
|
||||||
|
|
||||||
obj-$(CONFIG_KUNIT) += kunit/
|
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.
|
||||||
|
ifeq ($(CONFIG_KUNIT), m)
|
||||||
|
obj-y += kunit/hooks.o
|
||||||
|
else
|
||||||
|
obj-$(CONFIG_KUNIT) += kunit/hooks.o
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(CONFIG_DEBUG_KOBJECT),y)
|
ifeq ($(CONFIG_DEBUG_KOBJECT),y)
|
||||||
CFLAGS_kobject.o += -DDEBUG
|
CFLAGS_kobject.o += -DDEBUG
|
||||||
@@ -369,6 +377,7 @@ obj-$(CONFIG_PLDMFW) += pldmfw/
|
|||||||
CFLAGS_bitfield_kunit.o := $(DISABLE_STRUCTLEAK_PLUGIN)
|
CFLAGS_bitfield_kunit.o := $(DISABLE_STRUCTLEAK_PLUGIN)
|
||||||
obj-$(CONFIG_BITFIELD_KUNIT) += bitfield_kunit.o
|
obj-$(CONFIG_BITFIELD_KUNIT) += bitfield_kunit.o
|
||||||
obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
|
obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
|
||||||
|
obj-$(CONFIG_HASHTABLE_KUNIT_TEST) += hashtable_test.o
|
||||||
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
|
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
|
||||||
obj-$(CONFIG_BITS_TEST) += test_bits.o
|
obj-$(CONFIG_BITS_TEST) += test_bits.o
|
||||||
obj-$(CONFIG_CMDLINE_KUNIT_TEST) += cmdline_kunit.o
|
obj-$(CONFIG_CMDLINE_KUNIT_TEST) += cmdline_kunit.o
|
||||||
|
317
lib/hashtable_test.c
Normal file
317
lib/hashtable_test.c
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* KUnit test for the Kernel Hashtable structures.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022, Google LLC.
|
||||||
|
* Author: Rae Moar <rmoar@google.com>
|
||||||
|
*/
|
||||||
|
#include <kunit/test.h>
|
||||||
|
|
||||||
|
#include <linux/hashtable.h>
|
||||||
|
|
||||||
|
struct hashtable_test_entry {
|
||||||
|
int key;
|
||||||
|
int data;
|
||||||
|
struct hlist_node node;
|
||||||
|
int visited;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void hashtable_test_hash_init(struct kunit *test)
|
||||||
|
{
|
||||||
|
/* Test the different ways of initialising a hashtable. */
|
||||||
|
DEFINE_HASHTABLE(hash1, 2);
|
||||||
|
DECLARE_HASHTABLE(hash2, 3);
|
||||||
|
|
||||||
|
/* When using DECLARE_HASHTABLE, must use hash_init to
|
||||||
|
* initialize the hashtable.
|
||||||
|
*/
|
||||||
|
hash_init(hash2);
|
||||||
|
|
||||||
|
KUNIT_EXPECT_TRUE(test, hash_empty(hash1));
|
||||||
|
KUNIT_EXPECT_TRUE(test, hash_empty(hash2));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_empty(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry a;
|
||||||
|
DEFINE_HASHTABLE(hash, 1);
|
||||||
|
|
||||||
|
KUNIT_EXPECT_TRUE(test, hash_empty(hash));
|
||||||
|
|
||||||
|
a.key = 1;
|
||||||
|
a.data = 13;
|
||||||
|
hash_add(hash, &a.node, a.key);
|
||||||
|
|
||||||
|
/* Hashtable should no longer be empty. */
|
||||||
|
KUNIT_EXPECT_FALSE(test, hash_empty(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_hashed(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry a, b;
|
||||||
|
DEFINE_HASHTABLE(hash, 4);
|
||||||
|
|
||||||
|
a.key = 1;
|
||||||
|
a.data = 13;
|
||||||
|
hash_add(hash, &a.node, a.key);
|
||||||
|
b.key = 1;
|
||||||
|
b.data = 2;
|
||||||
|
hash_add(hash, &b.node, b.key);
|
||||||
|
|
||||||
|
KUNIT_EXPECT_TRUE(test, hash_hashed(&a.node));
|
||||||
|
KUNIT_EXPECT_TRUE(test, hash_hashed(&b.node));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_add(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry a, b, *x;
|
||||||
|
int bkt;
|
||||||
|
DEFINE_HASHTABLE(hash, 3);
|
||||||
|
|
||||||
|
a.key = 1;
|
||||||
|
a.data = 13;
|
||||||
|
a.visited = 0;
|
||||||
|
hash_add(hash, &a.node, a.key);
|
||||||
|
b.key = 2;
|
||||||
|
b.data = 10;
|
||||||
|
b.visited = 0;
|
||||||
|
hash_add(hash, &b.node, b.key);
|
||||||
|
|
||||||
|
hash_for_each(hash, bkt, x, node) {
|
||||||
|
x->visited++;
|
||||||
|
if (x->key == a.key)
|
||||||
|
KUNIT_EXPECT_EQ(test, x->data, 13);
|
||||||
|
else if (x->key == b.key)
|
||||||
|
KUNIT_EXPECT_EQ(test, x->data, 10);
|
||||||
|
else
|
||||||
|
KUNIT_FAIL(test, "Unexpected key in hashtable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Both entries should have been visited exactly once. */
|
||||||
|
KUNIT_EXPECT_EQ(test, a.visited, 1);
|
||||||
|
KUNIT_EXPECT_EQ(test, b.visited, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_del(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry a, b, *x;
|
||||||
|
DEFINE_HASHTABLE(hash, 6);
|
||||||
|
|
||||||
|
a.key = 1;
|
||||||
|
a.data = 13;
|
||||||
|
hash_add(hash, &a.node, a.key);
|
||||||
|
b.key = 2;
|
||||||
|
b.data = 10;
|
||||||
|
b.visited = 0;
|
||||||
|
hash_add(hash, &b.node, b.key);
|
||||||
|
|
||||||
|
hash_del(&b.node);
|
||||||
|
hash_for_each_possible(hash, x, node, b.key) {
|
||||||
|
x->visited++;
|
||||||
|
KUNIT_EXPECT_NE(test, x->key, b.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The deleted entry should not have been visited. */
|
||||||
|
KUNIT_EXPECT_EQ(test, b.visited, 0);
|
||||||
|
|
||||||
|
hash_del(&a.node);
|
||||||
|
|
||||||
|
/* The hashtable should be empty. */
|
||||||
|
KUNIT_EXPECT_TRUE(test, hash_empty(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_for_each(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry entries[3];
|
||||||
|
struct hashtable_test_entry *x;
|
||||||
|
int bkt, i, j, count;
|
||||||
|
DEFINE_HASHTABLE(hash, 3);
|
||||||
|
|
||||||
|
/* Add three entries to the hashtable. */
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
entries[i].key = i;
|
||||||
|
entries[i].data = i + 10;
|
||||||
|
entries[i].visited = 0;
|
||||||
|
hash_add(hash, &entries[i].node, entries[i].key);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
hash_for_each(hash, bkt, x, node) {
|
||||||
|
x->visited += 1;
|
||||||
|
KUNIT_ASSERT_GE_MSG(test, x->key, 0, "Unexpected key in hashtable.");
|
||||||
|
KUNIT_ASSERT_LT_MSG(test, x->key, 3, "Unexpected key in hashtable.");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should have visited each entry exactly once. */
|
||||||
|
KUNIT_EXPECT_EQ(test, count, 3);
|
||||||
|
for (j = 0; j < 3; j++)
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[j].visited, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_for_each_safe(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry entries[3];
|
||||||
|
struct hashtable_test_entry *x;
|
||||||
|
struct hlist_node *tmp;
|
||||||
|
int bkt, i, j, count;
|
||||||
|
DEFINE_HASHTABLE(hash, 3);
|
||||||
|
|
||||||
|
/* Add three entries to the hashtable. */
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
entries[i].key = i;
|
||||||
|
entries[i].data = i + 10;
|
||||||
|
entries[i].visited = 0;
|
||||||
|
hash_add(hash, &entries[i].node, entries[i].key);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
hash_for_each_safe(hash, bkt, tmp, x, node) {
|
||||||
|
x->visited += 1;
|
||||||
|
KUNIT_ASSERT_GE_MSG(test, x->key, 0, "Unexpected key in hashtable.");
|
||||||
|
KUNIT_ASSERT_LT_MSG(test, x->key, 3, "Unexpected key in hashtable.");
|
||||||
|
count++;
|
||||||
|
|
||||||
|
/* Delete entry during loop. */
|
||||||
|
hash_del(&x->node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should have visited each entry exactly once. */
|
||||||
|
KUNIT_EXPECT_EQ(test, count, 3);
|
||||||
|
for (j = 0; j < 3; j++)
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[j].visited, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_for_each_possible(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry entries[4];
|
||||||
|
struct hashtable_test_entry *x, *y;
|
||||||
|
int buckets[2];
|
||||||
|
int bkt, i, j, count;
|
||||||
|
DEFINE_HASHTABLE(hash, 5);
|
||||||
|
|
||||||
|
/* Add three entries with key = 0 to the hashtable. */
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
entries[i].key = 0;
|
||||||
|
entries[i].data = i;
|
||||||
|
entries[i].visited = 0;
|
||||||
|
hash_add(hash, &entries[i].node, entries[i].key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add an entry with key = 1. */
|
||||||
|
entries[3].key = 1;
|
||||||
|
entries[3].data = 3;
|
||||||
|
entries[3].visited = 0;
|
||||||
|
hash_add(hash, &entries[3].node, entries[3].key);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
hash_for_each_possible(hash, x, node, 0) {
|
||||||
|
x->visited += 1;
|
||||||
|
KUNIT_ASSERT_GE_MSG(test, x->data, 0, "Unexpected data in hashtable.");
|
||||||
|
KUNIT_ASSERT_LT_MSG(test, x->data, 4, "Unexpected data in hashtable.");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should have visited each entry with key = 0 exactly once. */
|
||||||
|
for (j = 0; j < 3; j++)
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[j].visited, 1);
|
||||||
|
|
||||||
|
/* Save the buckets for the different keys. */
|
||||||
|
hash_for_each(hash, bkt, y, node) {
|
||||||
|
KUNIT_ASSERT_GE_MSG(test, y->key, 0, "Unexpected key in hashtable.");
|
||||||
|
KUNIT_ASSERT_LE_MSG(test, y->key, 1, "Unexpected key in hashtable.");
|
||||||
|
buckets[y->key] = bkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If entry with key = 1 is in the same bucket as the entries with
|
||||||
|
* key = 0, check it was visited. Otherwise ensure that only three
|
||||||
|
* entries were visited.
|
||||||
|
*/
|
||||||
|
if (buckets[0] == buckets[1]) {
|
||||||
|
KUNIT_EXPECT_EQ(test, count, 4);
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[3].visited, 1);
|
||||||
|
} else {
|
||||||
|
KUNIT_EXPECT_EQ(test, count, 3);
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[3].visited, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hashtable_test_hash_for_each_possible_safe(struct kunit *test)
|
||||||
|
{
|
||||||
|
struct hashtable_test_entry entries[4];
|
||||||
|
struct hashtable_test_entry *x, *y;
|
||||||
|
struct hlist_node *tmp;
|
||||||
|
int buckets[2];
|
||||||
|
int bkt, i, j, count;
|
||||||
|
DEFINE_HASHTABLE(hash, 5);
|
||||||
|
|
||||||
|
/* Add three entries with key = 0 to the hashtable. */
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
entries[i].key = 0;
|
||||||
|
entries[i].data = i;
|
||||||
|
entries[i].visited = 0;
|
||||||
|
hash_add(hash, &entries[i].node, entries[i].key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add an entry with key = 1. */
|
||||||
|
entries[3].key = 1;
|
||||||
|
entries[3].data = 3;
|
||||||
|
entries[3].visited = 0;
|
||||||
|
hash_add(hash, &entries[3].node, entries[3].key);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
hash_for_each_possible_safe(hash, x, tmp, node, 0) {
|
||||||
|
x->visited += 1;
|
||||||
|
KUNIT_ASSERT_GE_MSG(test, x->data, 0, "Unexpected data in hashtable.");
|
||||||
|
KUNIT_ASSERT_LT_MSG(test, x->data, 4, "Unexpected data in hashtable.");
|
||||||
|
count++;
|
||||||
|
|
||||||
|
/* Delete entry during loop. */
|
||||||
|
hash_del(&x->node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Should have visited each entry with key = 0 exactly once. */
|
||||||
|
for (j = 0; j < 3; j++)
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[j].visited, 1);
|
||||||
|
|
||||||
|
/* Save the buckets for the different keys. */
|
||||||
|
hash_for_each(hash, bkt, y, node) {
|
||||||
|
KUNIT_ASSERT_GE_MSG(test, y->key, 0, "Unexpected key in hashtable.");
|
||||||
|
KUNIT_ASSERT_LE_MSG(test, y->key, 1, "Unexpected key in hashtable.");
|
||||||
|
buckets[y->key] = bkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If entry with key = 1 is in the same bucket as the entries with
|
||||||
|
* key = 0, check it was visited. Otherwise ensure that only three
|
||||||
|
* entries were visited.
|
||||||
|
*/
|
||||||
|
if (buckets[0] == buckets[1]) {
|
||||||
|
KUNIT_EXPECT_EQ(test, count, 4);
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[3].visited, 1);
|
||||||
|
} else {
|
||||||
|
KUNIT_EXPECT_EQ(test, count, 3);
|
||||||
|
KUNIT_EXPECT_EQ(test, entries[3].visited, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct kunit_case hashtable_test_cases[] = {
|
||||||
|
KUNIT_CASE(hashtable_test_hash_init),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_empty),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_hashed),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_add),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_del),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_for_each),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_for_each_safe),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_for_each_possible),
|
||||||
|
KUNIT_CASE(hashtable_test_hash_for_each_possible_safe),
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct kunit_suite hashtable_test_module = {
|
||||||
|
.name = "hashtable",
|
||||||
|
.test_cases = hashtable_test_cases,
|
||||||
|
};
|
||||||
|
|
||||||
|
kunit_test_suites(&hashtable_test_module);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
@@ -2,6 +2,7 @@ obj-$(CONFIG_KUNIT) += kunit.o
|
|||||||
|
|
||||||
kunit-objs += test.o \
|
kunit-objs += test.o \
|
||||||
resource.o \
|
resource.o \
|
||||||
|
static_stub.o \
|
||||||
string-stream.o \
|
string-stream.o \
|
||||||
assert.o \
|
assert.o \
|
||||||
try-catch.o \
|
try-catch.o \
|
||||||
@@ -11,6 +12,9 @@ ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
|
|||||||
kunit-objs += debugfs.o
|
kunit-objs += debugfs.o
|
||||||
endif
|
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
|
obj-$(CONFIG_KUNIT_TEST) += kunit-test.o
|
||||||
|
|
||||||
# string-stream-test compiles built-in only.
|
# string-stream-test compiles built-in only.
|
||||||
|
31
lib/kunit/hooks-impl.h
Normal file
31
lib/kunit/hooks-impl.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Declarations for hook implementations.
|
||||||
|
*
|
||||||
|
* These will be set as the function pointers in struct kunit_hook_table,
|
||||||
|
* found in include/kunit/test-bug.h.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023, Google LLC.
|
||||||
|
* Author: David Gow <davidgow@google.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _KUNIT_HOOKS_IMPL_H
|
||||||
|
#define _KUNIT_HOOKS_IMPL_H
|
||||||
|
|
||||||
|
#include <kunit/test-bug.h>
|
||||||
|
|
||||||
|
/* List of declarations. */
|
||||||
|
void __printf(3, 4) __kunit_fail_current_test_impl(const char *file,
|
||||||
|
int line,
|
||||||
|
const char *fmt, ...);
|
||||||
|
void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr);
|
||||||
|
|
||||||
|
/* Code to set all of the function pointers. */
|
||||||
|
static inline void kunit_install_hooks(void)
|
||||||
|
{
|
||||||
|
/* Install the KUnit hook functions. */
|
||||||
|
kunit_hooks.fail_current_test = __kunit_fail_current_test_impl;
|
||||||
|
kunit_hooks.get_static_stub_address = __kunit_get_static_stub_address_impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _KUNIT_HOOKS_IMPL_H */
|
21
lib/kunit/hooks.c
Normal file
21
lib/kunit/hooks.c
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// 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>
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <kunit/test-bug.h>
|
||||||
|
|
||||||
|
DEFINE_STATIC_KEY_FALSE(kunit_running);
|
||||||
|
EXPORT_SYMBOL(kunit_running);
|
||||||
|
|
||||||
|
/* Function pointers for hooks. */
|
||||||
|
struct kunit_hooks_table kunit_hooks;
|
||||||
|
EXPORT_SYMBOL(kunit_hooks);
|
||||||
|
|
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <kunit/test.h>
|
#include <kunit/test.h>
|
||||||
|
#include <kunit/static_stub.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is the most fundamental element of KUnit, the test case. A test case
|
* This is the most fundamental element of KUnit, the test case. A test case
|
||||||
@@ -130,6 +131,42 @@ static void example_all_expect_macros_test(struct kunit *test)
|
|||||||
KUNIT_ASSERT_GT_MSG(test, sizeof(int), 0, "Your ints are 0-bit?!");
|
KUNIT_ASSERT_GT_MSG(test, sizeof(int), 0, "Your ints are 0-bit?!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This is a function we'll replace with static stubs. */
|
||||||
|
static int add_one(int i)
|
||||||
|
{
|
||||||
|
/* This will trigger the stub if active. */
|
||||||
|
KUNIT_STATIC_STUB_REDIRECT(add_one, i);
|
||||||
|
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is used as a replacement for the above function. */
|
||||||
|
static int subtract_one(int i)
|
||||||
|
{
|
||||||
|
/* We don't need to trigger the stub from the replacement. */
|
||||||
|
|
||||||
|
return i - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test shows the use of static stubs.
|
||||||
|
*/
|
||||||
|
static void example_static_stub_test(struct kunit *test)
|
||||||
|
{
|
||||||
|
/* By default, function is not stubbed. */
|
||||||
|
KUNIT_EXPECT_EQ(test, add_one(1), 2);
|
||||||
|
|
||||||
|
/* Replace add_one() with subtract_one(). */
|
||||||
|
kunit_activate_static_stub(test, add_one, subtract_one);
|
||||||
|
|
||||||
|
/* add_one() is now replaced. */
|
||||||
|
KUNIT_EXPECT_EQ(test, add_one(1), 0);
|
||||||
|
|
||||||
|
/* Return add_one() to normal. */
|
||||||
|
kunit_deactivate_static_stub(test, add_one);
|
||||||
|
KUNIT_EXPECT_EQ(test, add_one(1), 2);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Here we make a list of all the test cases we want to add to the test suite
|
* Here we make a list of all the test cases we want to add to the test suite
|
||||||
* below.
|
* below.
|
||||||
@@ -145,6 +182,7 @@ static struct kunit_case example_test_cases[] = {
|
|||||||
KUNIT_CASE(example_skip_test),
|
KUNIT_CASE(example_skip_test),
|
||||||
KUNIT_CASE(example_mark_skipped_test),
|
KUNIT_CASE(example_mark_skipped_test),
|
||||||
KUNIT_CASE(example_all_expect_macros_test),
|
KUNIT_CASE(example_all_expect_macros_test),
|
||||||
|
KUNIT_CASE(example_static_stub_test),
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
123
lib/kunit/static_stub.c
Normal file
123
lib/kunit/static_stub.c
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* KUnit function redirection (static stubbing) API.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022, Google LLC.
|
||||||
|
* Author: David Gow <davidgow@google.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <kunit/test.h>
|
||||||
|
#include <kunit/static_stub.h>
|
||||||
|
#include "hooks-impl.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* Context for a static stub. This is stored in the resource data. */
|
||||||
|
struct kunit_static_stub_ctx {
|
||||||
|
void *real_fn_addr;
|
||||||
|
void *replacement_addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void __kunit_static_stub_resource_free(struct kunit_resource *res)
|
||||||
|
{
|
||||||
|
kfree(res->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Matching function for kunit_find_resource(). match_data is real_fn_addr. */
|
||||||
|
static bool __kunit_static_stub_resource_match(struct kunit *test,
|
||||||
|
struct kunit_resource *res,
|
||||||
|
void *match_real_fn_addr)
|
||||||
|
{
|
||||||
|
/* This pointer is only valid if res is a static stub resource. */
|
||||||
|
struct kunit_static_stub_ctx *ctx = res->data;
|
||||||
|
|
||||||
|
/* Make sure the resource is a static stub resource. */
|
||||||
|
if (res->free != &__kunit_static_stub_resource_free)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ctx->real_fn_addr == match_real_fn_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hook to return the address of the replacement function. */
|
||||||
|
void *__kunit_get_static_stub_address_impl(struct kunit *test, void *real_fn_addr)
|
||||||
|
{
|
||||||
|
struct kunit_resource *res;
|
||||||
|
struct kunit_static_stub_ctx *ctx;
|
||||||
|
void *replacement_addr;
|
||||||
|
|
||||||
|
res = kunit_find_resource(test,
|
||||||
|
__kunit_static_stub_resource_match,
|
||||||
|
real_fn_addr);
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
ctx = res->data;
|
||||||
|
replacement_addr = ctx->replacement_addr;
|
||||||
|
kunit_put_resource(res);
|
||||||
|
return replacement_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kunit_deactivate_static_stub(struct kunit *test, void *real_fn_addr)
|
||||||
|
{
|
||||||
|
struct kunit_resource *res;
|
||||||
|
|
||||||
|
KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL,
|
||||||
|
"Tried to deactivate a NULL stub.");
|
||||||
|
|
||||||
|
/* Look up the existing stub for this function. */
|
||||||
|
res = kunit_find_resource(test,
|
||||||
|
__kunit_static_stub_resource_match,
|
||||||
|
real_fn_addr);
|
||||||
|
|
||||||
|
/* Error out if the stub doesn't exist. */
|
||||||
|
KUNIT_ASSERT_PTR_NE_MSG(test, res, NULL,
|
||||||
|
"Tried to deactivate a nonexistent stub.");
|
||||||
|
|
||||||
|
/* Free the stub. We 'put' twice, as we got a reference
|
||||||
|
* from kunit_find_resource()
|
||||||
|
*/
|
||||||
|
kunit_remove_resource(test, res);
|
||||||
|
kunit_put_resource(res);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(kunit_deactivate_static_stub);
|
||||||
|
|
||||||
|
/* Helper function for kunit_activate_static_stub(). The macro does
|
||||||
|
* typechecking, so use it instead.
|
||||||
|
*/
|
||||||
|
void __kunit_activate_static_stub(struct kunit *test,
|
||||||
|
void *real_fn_addr,
|
||||||
|
void *replacement_addr)
|
||||||
|
{
|
||||||
|
struct kunit_static_stub_ctx *ctx;
|
||||||
|
struct kunit_resource *res;
|
||||||
|
|
||||||
|
KUNIT_ASSERT_PTR_NE_MSG(test, real_fn_addr, NULL,
|
||||||
|
"Tried to activate a stub for function NULL");
|
||||||
|
|
||||||
|
/* If the replacement address is NULL, deactivate the stub. */
|
||||||
|
if (!replacement_addr) {
|
||||||
|
kunit_deactivate_static_stub(test, replacement_addr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look up any existing stubs for this function, and replace them. */
|
||||||
|
res = kunit_find_resource(test,
|
||||||
|
__kunit_static_stub_resource_match,
|
||||||
|
real_fn_addr);
|
||||||
|
if (res) {
|
||||||
|
ctx = res->data;
|
||||||
|
ctx->replacement_addr = replacement_addr;
|
||||||
|
|
||||||
|
/* We got an extra reference from find_resource(), so put it. */
|
||||||
|
kunit_put_resource(res);
|
||||||
|
} else {
|
||||||
|
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
|
||||||
|
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
|
||||||
|
ctx->real_fn_addr = real_fn_addr;
|
||||||
|
ctx->replacement_addr = replacement_addr;
|
||||||
|
res = kunit_alloc_resource(test, NULL,
|
||||||
|
&__kunit_static_stub_resource_free,
|
||||||
|
GFP_KERNEL, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(__kunit_activate_static_stub);
|
@@ -17,17 +17,14 @@
|
|||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
|
|
||||||
#include "debugfs.h"
|
#include "debugfs.h"
|
||||||
|
#include "hooks-impl.h"
|
||||||
#include "string-stream.h"
|
#include "string-stream.h"
|
||||||
#include "try-catch-impl.h"
|
#include "try-catch-impl.h"
|
||||||
|
|
||||||
DEFINE_STATIC_KEY_FALSE(kunit_running);
|
|
||||||
EXPORT_SYMBOL_GPL(kunit_running);
|
|
||||||
|
|
||||||
#if IS_BUILTIN(CONFIG_KUNIT)
|
|
||||||
/*
|
/*
|
||||||
* Fail the current test and print an error message to the log.
|
* Hook to 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 __printf(3, 4) __kunit_fail_current_test_impl(const char *file, int line, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list args;
|
va_list args;
|
||||||
int len;
|
int len;
|
||||||
@@ -54,8 +51,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_err(current->kunit_test, "%s:%d: %s", file, line, buffer);
|
||||||
kunit_kfree(current->kunit_test, buffer);
|
kunit_kfree(current->kunit_test, buffer);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(__kunit_fail_current_test);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enable KUnit tests to run.
|
* Enable KUnit tests to run.
|
||||||
@@ -778,6 +773,9 @@ EXPORT_SYMBOL_GPL(kunit_cleanup);
|
|||||||
|
|
||||||
static int __init kunit_init(void)
|
static int __init kunit_init(void)
|
||||||
{
|
{
|
||||||
|
/* Install the KUnit hook functions. */
|
||||||
|
kunit_install_hooks();
|
||||||
|
|
||||||
kunit_debugfs_init();
|
kunit_debugfs_init();
|
||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
return register_module_notifier(&kunit_mod_nb);
|
return register_module_notifier(&kunit_mod_nb);
|
||||||
@@ -789,6 +787,7 @@ late_initcall(kunit_init);
|
|||||||
|
|
||||||
static void __exit kunit_exit(void)
|
static void __exit kunit_exit(void)
|
||||||
{
|
{
|
||||||
|
memset(&kunit_hooks, 0, sizeof(kunit_hooks));
|
||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
unregister_module_notifier(&kunit_mod_nb);
|
unregister_module_notifier(&kunit_mod_nb);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -77,11 +77,8 @@ def config_tests(linux: kunit_kernel.LinuxSourceTree,
|
|||||||
config_start = time.time()
|
config_start = time.time()
|
||||||
success = linux.build_reconfig(request.build_dir, request.make_options)
|
success = linux.build_reconfig(request.build_dir, request.make_options)
|
||||||
config_end = time.time()
|
config_end = time.time()
|
||||||
if not success:
|
status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE
|
||||||
return KunitResult(KunitStatus.CONFIG_FAILURE,
|
return KunitResult(status, config_end - config_start)
|
||||||
config_end - config_start)
|
|
||||||
return KunitResult(KunitStatus.SUCCESS,
|
|
||||||
config_end - config_start)
|
|
||||||
|
|
||||||
def build_tests(linux: kunit_kernel.LinuxSourceTree,
|
def build_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||||
request: KunitBuildRequest) -> KunitResult:
|
request: KunitBuildRequest) -> KunitResult:
|
||||||
@@ -92,14 +89,8 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree,
|
|||||||
request.build_dir,
|
request.build_dir,
|
||||||
request.make_options)
|
request.make_options)
|
||||||
build_end = time.time()
|
build_end = time.time()
|
||||||
if not success:
|
status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE
|
||||||
return KunitResult(KunitStatus.BUILD_FAILURE,
|
return KunitResult(status, build_end - build_start)
|
||||||
build_end - build_start)
|
|
||||||
if not success:
|
|
||||||
return KunitResult(KunitStatus.BUILD_FAILURE,
|
|
||||||
build_end - build_start)
|
|
||||||
return KunitResult(KunitStatus.SUCCESS,
|
|
||||||
build_end - build_start)
|
|
||||||
|
|
||||||
def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
|
def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||||
request: KunitBuildRequest) -> KunitResult:
|
request: KunitBuildRequest) -> KunitResult:
|
||||||
@@ -145,7 +136,7 @@ def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -
|
|||||||
tests = _list_tests(linux, request)
|
tests = _list_tests(linux, request)
|
||||||
if request.run_isolated == 'test':
|
if request.run_isolated == 'test':
|
||||||
filter_globs = tests
|
filter_globs = tests
|
||||||
if request.run_isolated == 'suite':
|
elif request.run_isolated == 'suite':
|
||||||
filter_globs = _suites_from_test_list(tests)
|
filter_globs = _suites_from_test_list(tests)
|
||||||
# Apply the test-part of the user's glob, if present.
|
# Apply the test-part of the user's glob, if present.
|
||||||
if '.' in request.filter_glob:
|
if '.' in request.filter_glob:
|
||||||
@@ -395,6 +386,95 @@ def tree_from_args(cli_args: argparse.Namespace) -> kunit_kernel.LinuxSourceTree
|
|||||||
extra_qemu_args=qemu_args)
|
extra_qemu_args=qemu_args)
|
||||||
|
|
||||||
|
|
||||||
|
def run_handler(cli_args):
|
||||||
|
if not os.path.exists(cli_args.build_dir):
|
||||||
|
os.mkdir(cli_args.build_dir)
|
||||||
|
|
||||||
|
linux = tree_from_args(cli_args)
|
||||||
|
request = KunitRequest(build_dir=cli_args.build_dir,
|
||||||
|
make_options=cli_args.make_options,
|
||||||
|
jobs=cli_args.jobs,
|
||||||
|
raw_output=cli_args.raw_output,
|
||||||
|
json=cli_args.json,
|
||||||
|
timeout=cli_args.timeout,
|
||||||
|
filter_glob=cli_args.filter_glob,
|
||||||
|
kernel_args=cli_args.kernel_args,
|
||||||
|
run_isolated=cli_args.run_isolated)
|
||||||
|
result = run_tests(linux, request)
|
||||||
|
if result.status != KunitStatus.SUCCESS:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def config_handler(cli_args):
|
||||||
|
if cli_args.build_dir and (
|
||||||
|
not os.path.exists(cli_args.build_dir)):
|
||||||
|
os.mkdir(cli_args.build_dir)
|
||||||
|
|
||||||
|
linux = tree_from_args(cli_args)
|
||||||
|
request = KunitConfigRequest(build_dir=cli_args.build_dir,
|
||||||
|
make_options=cli_args.make_options)
|
||||||
|
result = config_tests(linux, request)
|
||||||
|
stdout.print_with_timestamp((
|
||||||
|
'Elapsed time: %.3fs\n') % (
|
||||||
|
result.elapsed_time))
|
||||||
|
if result.status != KunitStatus.SUCCESS:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def build_handler(cli_args):
|
||||||
|
linux = tree_from_args(cli_args)
|
||||||
|
request = KunitBuildRequest(build_dir=cli_args.build_dir,
|
||||||
|
make_options=cli_args.make_options,
|
||||||
|
jobs=cli_args.jobs)
|
||||||
|
result = config_and_build_tests(linux, request)
|
||||||
|
stdout.print_with_timestamp((
|
||||||
|
'Elapsed time: %.3fs\n') % (
|
||||||
|
result.elapsed_time))
|
||||||
|
if result.status != KunitStatus.SUCCESS:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def exec_handler(cli_args):
|
||||||
|
linux = tree_from_args(cli_args)
|
||||||
|
exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
|
||||||
|
build_dir=cli_args.build_dir,
|
||||||
|
json=cli_args.json,
|
||||||
|
timeout=cli_args.timeout,
|
||||||
|
filter_glob=cli_args.filter_glob,
|
||||||
|
kernel_args=cli_args.kernel_args,
|
||||||
|
run_isolated=cli_args.run_isolated)
|
||||||
|
result = exec_tests(linux, exec_request)
|
||||||
|
stdout.print_with_timestamp((
|
||||||
|
'Elapsed time: %.3fs\n') % (result.elapsed_time))
|
||||||
|
if result.status != KunitStatus.SUCCESS:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_handler(cli_args):
|
||||||
|
if cli_args.file is None:
|
||||||
|
sys.stdin.reconfigure(errors='backslashreplace') # pytype: disable=attribute-error
|
||||||
|
kunit_output = sys.stdin
|
||||||
|
else:
|
||||||
|
with open(cli_args.file, 'r', errors='backslashreplace') as f:
|
||||||
|
kunit_output = f.read().splitlines()
|
||||||
|
# We know nothing about how the result was created!
|
||||||
|
metadata = kunit_json.Metadata()
|
||||||
|
request = KunitParseRequest(raw_output=cli_args.raw_output,
|
||||||
|
json=cli_args.json)
|
||||||
|
result, _ = parse_tests(request, metadata, kunit_output)
|
||||||
|
if result.status != KunitStatus.SUCCESS:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
subcommand_handlers_map = {
|
||||||
|
'run': run_handler,
|
||||||
|
'config': config_handler,
|
||||||
|
'build': build_handler,
|
||||||
|
'exec': exec_handler,
|
||||||
|
'parse': parse_handler
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Helps writing and running KUnit tests.')
|
description='Helps writing and running KUnit tests.')
|
||||||
@@ -438,78 +518,14 @@ def main(argv):
|
|||||||
if get_kernel_root_path():
|
if get_kernel_root_path():
|
||||||
os.chdir(get_kernel_root_path())
|
os.chdir(get_kernel_root_path())
|
||||||
|
|
||||||
if cli_args.subcommand == 'run':
|
subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None)
|
||||||
if not os.path.exists(cli_args.build_dir):
|
|
||||||
os.mkdir(cli_args.build_dir)
|
|
||||||
|
|
||||||
linux = tree_from_args(cli_args)
|
if subcomand_handler is None:
|
||||||
request = KunitRequest(build_dir=cli_args.build_dir,
|
|
||||||
make_options=cli_args.make_options,
|
|
||||||
jobs=cli_args.jobs,
|
|
||||||
raw_output=cli_args.raw_output,
|
|
||||||
json=cli_args.json,
|
|
||||||
timeout=cli_args.timeout,
|
|
||||||
filter_glob=cli_args.filter_glob,
|
|
||||||
kernel_args=cli_args.kernel_args,
|
|
||||||
run_isolated=cli_args.run_isolated)
|
|
||||||
result = run_tests(linux, request)
|
|
||||||
if result.status != KunitStatus.SUCCESS:
|
|
||||||
sys.exit(1)
|
|
||||||
elif cli_args.subcommand == 'config':
|
|
||||||
if cli_args.build_dir and (
|
|
||||||
not os.path.exists(cli_args.build_dir)):
|
|
||||||
os.mkdir(cli_args.build_dir)
|
|
||||||
|
|
||||||
linux = tree_from_args(cli_args)
|
|
||||||
request = KunitConfigRequest(build_dir=cli_args.build_dir,
|
|
||||||
make_options=cli_args.make_options)
|
|
||||||
result = config_tests(linux, request)
|
|
||||||
stdout.print_with_timestamp((
|
|
||||||
'Elapsed time: %.3fs\n') % (
|
|
||||||
result.elapsed_time))
|
|
||||||
if result.status != KunitStatus.SUCCESS:
|
|
||||||
sys.exit(1)
|
|
||||||
elif cli_args.subcommand == 'build':
|
|
||||||
linux = tree_from_args(cli_args)
|
|
||||||
request = KunitBuildRequest(build_dir=cli_args.build_dir,
|
|
||||||
make_options=cli_args.make_options,
|
|
||||||
jobs=cli_args.jobs)
|
|
||||||
result = config_and_build_tests(linux, request)
|
|
||||||
stdout.print_with_timestamp((
|
|
||||||
'Elapsed time: %.3fs\n') % (
|
|
||||||
result.elapsed_time))
|
|
||||||
if result.status != KunitStatus.SUCCESS:
|
|
||||||
sys.exit(1)
|
|
||||||
elif cli_args.subcommand == 'exec':
|
|
||||||
linux = tree_from_args(cli_args)
|
|
||||||
exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
|
|
||||||
build_dir=cli_args.build_dir,
|
|
||||||
json=cli_args.json,
|
|
||||||
timeout=cli_args.timeout,
|
|
||||||
filter_glob=cli_args.filter_glob,
|
|
||||||
kernel_args=cli_args.kernel_args,
|
|
||||||
run_isolated=cli_args.run_isolated)
|
|
||||||
result = exec_tests(linux, exec_request)
|
|
||||||
stdout.print_with_timestamp((
|
|
||||||
'Elapsed time: %.3fs\n') % (result.elapsed_time))
|
|
||||||
if result.status != KunitStatus.SUCCESS:
|
|
||||||
sys.exit(1)
|
|
||||||
elif cli_args.subcommand == 'parse':
|
|
||||||
if cli_args.file is None:
|
|
||||||
sys.stdin.reconfigure(errors='backslashreplace') # pytype: disable=attribute-error
|
|
||||||
kunit_output = sys.stdin
|
|
||||||
else:
|
|
||||||
with open(cli_args.file, 'r', errors='backslashreplace') as f:
|
|
||||||
kunit_output = f.read().splitlines()
|
|
||||||
# We know nothing about how the result was created!
|
|
||||||
metadata = kunit_json.Metadata()
|
|
||||||
request = KunitParseRequest(raw_output=cli_args.raw_output,
|
|
||||||
json=cli_args.json)
|
|
||||||
result, _ = parse_tests(request, metadata, kunit_output)
|
|
||||||
if result.status != KunitStatus.SUCCESS:
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
return
|
||||||
|
|
||||||
|
subcomand_handler(cli_args)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
|
Reference in New Issue
Block a user