mirror of
https://github.com/torvalds/linux.git
synced 2024-11-26 22:21:42 +00:00
linux-kselftest-kunit-6.3-rc1
This KUnit update for Linux 6.3-rc1 consists of cleanups, new features, and documentation updates: -- adds Function Redirection API to isolate the code being tested from other parts of the kernel. functionredirection.rst has the details. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEPZKym/RZuOCGeA/kCwJExA0NQxwFAmP1c3AACgkQCwJExA0N Qxxbwg//TK0YlpQhoO2AgqSp3F8QlXeFKNdm5rHjBBVMYOQOl6rEB+4uznm2AOD9 PZmQfAI+bcxMflSMDEBHEwbh6gLyZJKrsMsxuH2k/LQeWHAbuxHVq+/K4kqzhuhi QA4ZFKFqnHy+U7jCOGdMtrg9oyg7Glz00fq5pX2iz3FWsE/JpuDZ559RoB9zT9Pu VnZ+k42Svxkdmf8fXhSCH7C66k9fKkcQm7IGyVbnsWqmldCHpQ6kIjJVTeQSng4j tXkcys37I/d3/Ffz63rke7+WmJrQviL/gg3PqDmEEVxeX8T3GBT01uONTk+TqyWd GKudu1lfvuyylFMDoR/5gXr2hr5OJJTGjTfEtwWq7xM0NSiIFHS3/uEYZlE9g3+U z2/DKMWOHrzJ2G78dfi5fokFdMfGnz2hBCZa9czSxIbjafxLhjSgnt112mDvkJsZ leeVTB9x6g0b+VYwPKYa9gOmFQyZDGTTsJVT9iaAnhEvlxIRoqxZxzW/jFKgHV/r ZNRg/kcPfe7m6H15PEblFIuLC4LT/LtDxD8XvkKt42XnG2fuAPS20Jkv6/XB9Ew6 3H1Su27TXIksUD/Z/ZPP9mBno7rwOLrZUa4QNzXqi6q2sbdXP5apg96cPDU0gvI5 sq4zwLgHVuIQ8dfX/hgmqZ8VEcvSFDMINoS+SYGvKjxoTzvd+Sw= =PloE -----END PGP SIGNATURE----- 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:
commit
89f1a2440a
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
|
||||
=============
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
test
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
- 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``.
|
||||
|
||||
``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,6 @@ 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.
|
||||
|
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 */
|
||||
/*
|
||||
* 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>
|
||||
@ -17,6 +17,12 @@
|
||||
/* Static key if KUnit is running any tests. */
|
||||
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 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.
|
||||
*
|
||||
* 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)) { \
|
||||
__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__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
|
||||
const char *fmt, ...);
|
||||
|
||||
#else
|
||||
|
||||
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, ...) \
|
||||
__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, ...)
|
||||
{
|
||||
}
|
||||
#define kunit_fail_current_test(fmt, ...) do {} while (0)
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -2517,6 +2517,19 @@ config LIST_KUNIT_TEST
|
||||
|
||||
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
|
||||
tristate "KUnit test for linear_ranges"
|
||||
depends on KUNIT
|
||||
|
@ -126,6 +126,14 @@ 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.
|
||||
ifeq ($(CONFIG_KUNIT), m)
|
||||
obj-y += kunit/hooks.o
|
||||
else
|
||||
obj-$(CONFIG_KUNIT) += kunit/hooks.o
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_DEBUG_KOBJECT),y)
|
||||
CFLAGS_kobject.o += -DDEBUG
|
||||
@ -369,6 +377,7 @@ obj-$(CONFIG_PLDMFW) += pldmfw/
|
||||
CFLAGS_bitfield_kunit.o := $(DISABLE_STRUCTLEAK_PLUGIN)
|
||||
obj-$(CONFIG_BITFIELD_KUNIT) += bitfield_kunit.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_BITS_TEST) += test_bits.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 \
|
||||
resource.o \
|
||||
static_stub.o \
|
||||
string-stream.o \
|
||||
assert.o \
|
||||
try-catch.o \
|
||||
@ -11,6 +12,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.
|
||||
|
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/static_stub.h>
|
||||
|
||||
/*
|
||||
* 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?!");
|
||||
}
|
||||
|
||||
/* 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
|
||||
* below.
|
||||
@ -145,6 +182,7 @@ static struct kunit_case example_test_cases[] = {
|
||||
KUNIT_CASE(example_skip_test),
|
||||
KUNIT_CASE(example_mark_skipped_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 "debugfs.h"
|
||||
#include "hooks-impl.h"
|
||||
#include "string-stream.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;
|
||||
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_kfree(current->kunit_test, buffer);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__kunit_fail_current_test);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enable KUnit tests to run.
|
||||
@ -778,6 +773,9 @@ EXPORT_SYMBOL_GPL(kunit_cleanup);
|
||||
|
||||
static int __init kunit_init(void)
|
||||
{
|
||||
/* Install the KUnit hook functions. */
|
||||
kunit_install_hooks();
|
||||
|
||||
kunit_debugfs_init();
|
||||
#ifdef CONFIG_MODULES
|
||||
return register_module_notifier(&kunit_mod_nb);
|
||||
@ -789,6 +787,7 @@ late_initcall(kunit_init);
|
||||
|
||||
static void __exit kunit_exit(void)
|
||||
{
|
||||
memset(&kunit_hooks, 0, sizeof(kunit_hooks));
|
||||
#ifdef CONFIG_MODULES
|
||||
unregister_module_notifier(&kunit_mod_nb);
|
||||
#endif
|
||||
|
@ -77,11 +77,8 @@ def config_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
config_start = time.time()
|
||||
success = linux.build_reconfig(request.build_dir, request.make_options)
|
||||
config_end = time.time()
|
||||
if not success:
|
||||
return KunitResult(KunitStatus.CONFIG_FAILURE,
|
||||
config_end - config_start)
|
||||
return KunitResult(KunitStatus.SUCCESS,
|
||||
config_end - config_start)
|
||||
status = KunitStatus.SUCCESS if success else KunitStatus.CONFIG_FAILURE
|
||||
return KunitResult(status, config_end - config_start)
|
||||
|
||||
def build_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
request: KunitBuildRequest) -> KunitResult:
|
||||
@ -92,14 +89,8 @@ def build_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
request.build_dir,
|
||||
request.make_options)
|
||||
build_end = time.time()
|
||||
if not success:
|
||||
return KunitResult(KunitStatus.BUILD_FAILURE,
|
||||
build_end - build_start)
|
||||
if not success:
|
||||
return KunitResult(KunitStatus.BUILD_FAILURE,
|
||||
build_end - build_start)
|
||||
return KunitResult(KunitStatus.SUCCESS,
|
||||
build_end - build_start)
|
||||
status = KunitStatus.SUCCESS if success else KunitStatus.BUILD_FAILURE
|
||||
return KunitResult(status, build_end - build_start)
|
||||
|
||||
def config_and_build_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
request: KunitBuildRequest) -> KunitResult:
|
||||
@ -145,7 +136,7 @@ def exec_tests(linux: kunit_kernel.LinuxSourceTree, request: KunitExecRequest) -
|
||||
tests = _list_tests(linux, request)
|
||||
if request.run_isolated == 'test':
|
||||
filter_globs = tests
|
||||
if request.run_isolated == 'suite':
|
||||
elif request.run_isolated == 'suite':
|
||||
filter_globs = _suites_from_test_list(tests)
|
||||
# Apply the test-part of the user's glob, if present.
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Helps writing and running KUnit tests.')
|
||||
@ -438,78 +518,14 @@ def main(argv):
|
||||
if get_kernel_root_path():
|
||||
os.chdir(get_kernel_root_path())
|
||||
|
||||
if cli_args.subcommand == 'run':
|
||||
if not os.path.exists(cli_args.build_dir):
|
||||
os.mkdir(cli_args.build_dir)
|
||||
subcomand_handler = subcommand_handlers_map.get(cli_args.subcommand, None)
|
||||
|
||||
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)
|
||||
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:
|
||||
if subcomand_handler is None:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
subcomand_handler(cli_args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
|
Loading…
Reference in New Issue
Block a user