mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-06-21 15:43:21 +02:00
85347718ab
Some unit tests intentionally trigger warning backtraces by passing bad
parameters to kernel API functions. Such unit tests typically check the
return value from such calls, not the existence of the warning backtrace.
Such intentionally generated warning backtraces are neither desirable
nor useful for a number of reasons:
- They can result in overlooked real problems.
- A warning that suddenly starts to show up in unit tests needs to be
investigated and has to be marked to be ignored, for example by
adjusting filter scripts. Such filters are ad hoc because there is
no real standard format for warnings. On top of that, such filter
scripts would require constant maintenance.
Solve the problem by providing a means to suppress warning backtraces
originating from the current kthread while executing test code. Since
each KUnit test runs in its own kthread, this effectively scopes
suppression to the test that enabled it. Limit changes to generic code
to the absolute minimum.
Implementation details:
Suppression is integrated into the existing KUnit hooks infrastructure
in test-bug.h, reusing the kunit_running static branch for zero
overhead when no tests are running.
Suppression is checked at three points in the warning path:
- In warn_slowpath_fmt(), the check runs before any output, fully
suppressing both message and backtrace. This covers architectures
without __WARN_FLAGS.
- In __warn_printk(), the check suppresses the warning message text.
This covers architectures that define __WARN_FLAGS but not their own
__WARN_printf (arm64, loongarch, parisc, powerpc, riscv, sh), where
the message is printed before the trap enters __report_bug().
- In __report_bug(), the check runs before __warn() is called,
suppressing the backtrace and stack dump.
To avoid double-counting on architectures where both __warn_printk()
and __report_bug() run for the same warning, kunit_is_suppressed_warning()
takes a bool parameter: true to increment the suppression counter
(used in warn_slowpath_fmt and __report_bug), false to check only
(used in __warn_printk).
The suppression state is dynamically allocated via kunit_kzalloc() and
tied to the KUnit test lifecycle via kunit_add_action(), ensuring
automatic cleanup at test exit. Writer-side access to the global
suppression list is serialized with a spinlock; readers use RCU.
Two API forms are provided:
- kunit_warning_suppress(test) { ... }: scoped, uses __cleanup for
automatic teardown on scope exit, kunit_add_action() as safety net
for abnormal exits (e.g. kthread_exit from failed assertions).
Suppression handle is only accessible inside the block.
- kunit_start/end_suppress_warning(test): direct functions returning
an explicit handle, for retaining the handle within the test,
or for cross-function usage.
Link: https://lore.kernel.org/r/20260514-kunit_add_support-v11-1-b36a530a6d8f@redhat.com
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Alessandro Carminati <acarmina@redhat.com>
Reviewed-by: Kees Cook <kees@kernel.org>
Reviewed-by: David Gow <david@davidgow.net>
Signed-off-by: Albert Esteve <aesteve@redhat.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
98 lines
3.1 KiB
C
98 lines
3.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* KUnit API providing hooks for non-test code to interact with tests.
|
|
*
|
|
* Copyright (C) 2020, Google LLC.
|
|
* Author: Uriel Guajardo <urielguajardo@google.com>
|
|
*/
|
|
|
|
#ifndef _KUNIT_TEST_BUG_H
|
|
#define _KUNIT_TEST_BUG_H
|
|
|
|
#include <linux/stddef.h> /* for NULL */
|
|
#include <linux/types.h> /* for bool */
|
|
|
|
#if IS_ENABLED(CONFIG_KUNIT)
|
|
|
|
#include <linux/jump_label.h> /* For static branch */
|
|
#include <linux/sched.h>
|
|
|
|
/* 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);
|
|
bool (*is_suppressed_warning)(bool count);
|
|
} kunit_hooks;
|
|
|
|
/**
|
|
* kunit_get_current_test() - Return a pointer to the currently running
|
|
* KUnit test.
|
|
*
|
|
* If a KUnit test is running in the current task, returns a pointer to its
|
|
* associated struct kunit. This pointer can then be passed to any KUnit
|
|
* function or assertion. If no test is running (or a test is running in a
|
|
* different task), returns NULL.
|
|
*
|
|
* This function is safe to call even when KUnit is disabled. If CONFIG_KUNIT
|
|
* is not enabled, it will compile down to nothing and will return quickly no
|
|
* test is running.
|
|
*/
|
|
static inline struct kunit *kunit_get_current_test(void)
|
|
{
|
|
if (!static_branch_unlikely(&kunit_running))
|
|
return NULL;
|
|
|
|
return current->kunit_test;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
#define kunit_fail_current_test(fmt, ...) do { \
|
|
if (static_branch_unlikely(&kunit_running)) { \
|
|
/* Guaranteed to be non-NULL when kunit_running true*/ \
|
|
kunit_hooks.fail_current_test(__FILE__, __LINE__, \
|
|
fmt, ##__VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
/**
|
|
* kunit_is_suppressed_warning() - Check if warnings are being suppressed
|
|
* by the current KUnit test.
|
|
* @count: if true, increment the suppression counter on match.
|
|
*
|
|
* Returns true if the current task has active warning suppression.
|
|
* Uses the kunit_running static branch for zero overhead when no tests run.
|
|
*
|
|
* A single WARN*() may traverse multiple call sites in the warning path
|
|
* (e.g., __warn_printk() and __report_bug()). Pass @count = true at the
|
|
* primary suppression point to count each warning exactly once, and
|
|
* @count = false at secondary points to suppress output without
|
|
* inflating the count.
|
|
*/
|
|
static inline bool kunit_is_suppressed_warning(bool count)
|
|
{
|
|
if (!static_branch_unlikely(&kunit_running))
|
|
return false;
|
|
|
|
return kunit_hooks.is_suppressed_warning &&
|
|
kunit_hooks.is_suppressed_warning(count);
|
|
}
|
|
|
|
#else
|
|
|
|
static inline struct kunit *kunit_get_current_test(void) { return NULL; }
|
|
static inline bool kunit_is_suppressed_warning(bool count) { return false; }
|
|
|
|
#define kunit_fail_current_test(fmt, ...) do {} while (0)
|
|
|
|
#endif
|
|
|
|
#endif /* _KUNIT_TEST_BUG_H */
|