Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 62 additions & 1 deletion Documentation/dev-tools/kunit/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,67 @@ Alternatively, one can take full control over the error message by using
if (some_setup_function())
KUNIT_FAIL(test, "Failed to setup thing for testing");

Suppressing warning backtraces
------------------------------

Some unit tests trigger warning backtraces either intentionally or as a side
effect. Such backtraces are normally undesirable since they distract from
the actual test and may result in the impression that there is a problem.

Backtraces can be suppressed with **task-scoped suppression**: while
suppression is active on the current task, the backtrace and stack dump from
``WARN*()``, ``WARN_ON*()``, and related macros on that task are suppressed.
Three API forms are available, in order of convenience.

- Scoped suppression is the simplest form. Wrap the code that triggers
warnings in a ``kunit_warning_suppress()`` block:

.. code-block:: c

static void some_test(struct kunit *test)
{
kunit_warning_suppress(test) {
trigger_backtrace();
KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
}
}

.. note::
The warning count must be checked inside the block; the suppression handle
is not accessible after the block exits.

- Manual macros are useful when the suppressed region is large enough that
extra indentation is undesirable, or when the warning count needs to be
checked after suppression ends. ``KUNIT_START_SUPPRESSED_WARNING()`` must
appear before ``KUNIT_END_SUPPRESSED_WARNING()`` in the same scope.
Limited to one pair per scope.

.. code-block:: c

static void some_test(struct kunit *test)
{
KUNIT_START_SUPPRESSED_WARNING(test);
trigger_backtrace();
KUNIT_END_SUPPRESSED_WARNING(test);

KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
}

- Direct functions return an explicit handle pointer. Use them when the handle
needs to be retained or passed across helper functions:

.. code-block:: c

static void some_test(struct kunit *test)
{
struct kunit_suppressed_warning *w;

w = kunit_start_suppress_warning(test);
trigger_backtrace();
kunit_end_suppress_warning(test, w);

KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(w), 1);
}

Test Suites
~~~~~~~~~~~
Expand Down Expand Up @@ -1211,4 +1272,4 @@ For example:
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");

// Everything is cleaned up automatically when the test ends.
}
}
23 changes: 19 additions & 4 deletions drivers/gpu/drm/tests/drm_rect_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,16 @@ static void drm_test_rect_calc_hscale(struct kunit *test)
const struct drm_rect_scale_case *params = test->param_value;
int scaling_factor;

scaling_factor = drm_rect_calc_hscale(&params->src, &params->dst,
params->min_range, params->max_range);
/*
* drm_rect_calc_hscale() generates a warning backtrace whenever bad
* parameters are passed to it. This affects all unit tests with an
* error code in expected_scaling_factor.
*/
kunit_warning_suppress(test) {
scaling_factor = drm_rect_calc_hscale(&params->src, &params->dst,
params->min_range,
params->max_range);
}

KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor);
}
Expand All @@ -420,8 +428,15 @@ static void drm_test_rect_calc_vscale(struct kunit *test)
const struct drm_rect_scale_case *params = test->param_value;
int scaling_factor;

scaling_factor = drm_rect_calc_vscale(&params->src, &params->dst,
params->min_range, params->max_range);
/*
* drm_rect_calc_vscale() generates a warning backtrace whenever bad
* parameters are passed to it. This affects all unit tests with an
* error code in expected_scaling_factor.
*/
kunit_warning_suppress(test) {
scaling_factor = drm_rect_calc_vscale(&params->src, &params->dst,
params->min_range, params->max_range);
}

KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor);
}
Expand Down
25 changes: 25 additions & 0 deletions include/kunit/test-bug.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
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;

/**
Expand Down Expand Up @@ -60,9 +61,33 @@ static inline struct kunit *kunit_get_current_test(void)
} \
} 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)

Expand Down
138 changes: 138 additions & 0 deletions include/kunit/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -1795,4 +1795,142 @@ do { \
// include resource.h themselves if they need it.
#include <kunit/resource.h>

/*
* Warning backtrace suppression API.
*
* Suppresses WARN*() backtraces on the current task while active. Three forms
* are provided, in order of convenience:
*
* - Scoped: kunit_warning_suppress(test) { ... }
* Suppression is active for the duration of the block. On normal exit,
* the for-loop increment deactivates suppression. On early exit (break,
* return, goto), the __cleanup attribute fires. On kthread_exit() (e.g.,
* a failed KUnit assertion), kunit_add_action() cleans up at test
* teardown. The suppression handle is only accessible inside the block,
* so warning counts must be checked before the block exits.
*
* - Manual macros: KUNIT_[START|END]_SUPPRESSED_WARNING(test)
* Suppression spans an explicit range in the same scope. kunit_add_action()
* guarantees cleanup even if KUNIT_END_SUPPRESSED_WARNING() is not reached.
* Prefer this form when suppressing warnings across a large block where
* extra indentation is undesirable, or when the warning count needs to be
* checked after suppression ends. Limited to one pair per scope.
*
* - Direct: kunit_start_suppress_warning() / kunit_end_suppress_warning()
* The underlying functions, returning an explicit handle pointer. Use
* when the handle needs to be retained (e.g., for post-suppression
* count checks) or passed across helper functions.
*/
struct kunit_suppressed_warning;

struct kunit_suppressed_warning *
kunit_start_suppress_warning(struct kunit *test);
void kunit_end_suppress_warning(struct kunit *test,
struct kunit_suppressed_warning *w);
int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w);
void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp);
bool kunit_has_active_suppress_warning(void);

/**
* kunit_warning_suppress() - Suppress WARN*() backtraces for the duration
* of a block.
* @test: The test context object.
*
* Scoped form of the suppression API. Suppression starts when the block is
* entered and ends automatically when the block exits through any path. See
* the section comment above for the cleanup guarantees on each exit path.
* Fails the test if suppression is already active; nesting is not supported.
*
* The warning count can be checked inside the block via
* KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(). The handle is not accessible
* after the block exits.
*
* Example::
*
* kunit_warning_suppress(test) {
* trigger_warning();
* KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
* }
*/
#define kunit_warning_suppress(test) \
for (struct kunit_suppressed_warning *__kunit_suppress \
__cleanup(__kunit_suppress_auto_cleanup) = \
kunit_start_suppress_warning(test); \
__kunit_suppress; \
kunit_end_suppress_warning(test, __kunit_suppress), \
__kunit_suppress = NULL)

/**
* KUNIT_START_SUPPRESSED_WARNING() - Begin suppressing WARN*() backtraces.
* @test: The test context object.
*
* Manual form of the suppression API. Must be paired with
* KUNIT_END_SUPPRESSED_WARNING() in the same scope. See the section comment
* above for cleanup guarantees. Fails the test if suppression is already
* active; nesting is not supported. Limited to one pair per scope; use
* sequential kunit_warning_suppress() blocks or the direct function API
* when more than one suppression region is needed.
*
* Example::
*
* KUNIT_START_SUPPRESSED_WARNING(test);
* trigger_code_that_should_warn_once();
* KUNIT_END_SUPPRESSED_WARNING(test);
* KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
*/
#define KUNIT_START_SUPPRESSED_WARNING(test) \
struct kunit_suppressed_warning *__kunit_suppress = \
kunit_start_suppress_warning(test)

/**
* KUNIT_END_SUPPRESSED_WARNING() - End suppressing WARN*() backtraces.
* @test: The test context object.
*
* Deactivates suppression started by KUNIT_START_SUPPRESSED_WARNING().
* The warning count remains readable via KUNIT_SUPPRESSED_WARNING_COUNT()
* after this call.
*/
#define KUNIT_END_SUPPRESSED_WARNING(test) \
kunit_end_suppress_warning(test, __kunit_suppress)

/**
* KUNIT_SUPPRESSED_WARNING_COUNT() - Returns the suppressed warning count.
*
* Returns the number of WARN*() calls suppressed since the current
* suppression block started, or 0 if the handle is NULL. Usable inside a
* kunit_warning_suppress() block or after KUNIT_END_SUPPRESSED_WARNING().
*/
#define KUNIT_SUPPRESSED_WARNING_COUNT() \
kunit_suppressed_warning_count(__kunit_suppress)

/**
* KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT() - Sets an expectation that the
* suppressed warning count equals
* @expected.
* @test: The test context object.
* @expected: an expression that evaluates to the expected warning count.
*
* Sets an expectation that the number of suppressed WARN*() calls equals
* @expected. This is semantically equivalent to
* KUNIT_EXPECT_EQ(@test, KUNIT_SUPPRESSED_WARNING_COUNT(), @expected).
* See KUNIT_EXPECT_EQ() for more information.
*/
#define KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, expected) \
KUNIT_EXPECT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)

/**
* KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT() - Sets an assertion that the
* suppressed warning count equals
* @expected.
* @test: The test context object.
* @expected: an expression that evaluates to the expected warning count.
*
* Sets an assertion that the number of suppressed WARN*() calls equals
* @expected. This is the same as KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(),
* except it causes an assertion failure (see KUNIT_ASSERT_TRUE()) when the
* assertion is not met.
*/
#define KUNIT_ASSERT_SUPPRESSED_WARNING_COUNT(test, expected) \
KUNIT_ASSERT_EQ(test, KUNIT_SUPPRESSED_WARNING_COUNT(), expected)

#endif /* _KUNIT_TEST_H */
15 changes: 13 additions & 2 deletions kernel/panic.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <linux/sys_info.h>
#include <trace/events/error_report.h>
#include <asm/sections.h>
#include <kunit/test-bug.h>

#define PANIC_TIMER_STEP 100
#define PANIC_BLINK_SPD 18
Expand Down Expand Up @@ -1121,9 +1122,14 @@ void __warn(const char *file, int line, void *caller, unsigned taint,
void warn_slowpath_fmt(const char *file, int line, unsigned taint,
const char *fmt, ...)
{
bool rcu = warn_rcu_enter();
bool rcu;
struct warn_args args;

if (kunit_is_suppressed_warning(true))
return;

rcu = warn_rcu_enter();

pr_warn(CUT_HERE);

if (!fmt) {
Expand All @@ -1143,9 +1149,14 @@ EXPORT_SYMBOL(warn_slowpath_fmt);
#else
void __warn_printk(const char *fmt, ...)
{
bool rcu = warn_rcu_enter();
bool rcu;
va_list args;

if (kunit_is_suppressed_warning(false))
return;

rcu = warn_rcu_enter();

pr_warn(CUT_HERE);

va_start(args, fmt);
Expand Down
10 changes: 10 additions & 0 deletions lib/bug.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <linux/rculist.h>
#include <linux/ftrace.h>
#include <linux/context_tracking.h>
#include <kunit/test-bug.h>

extern struct bug_entry __start___bug_table[], __stop___bug_table[];

Expand Down Expand Up @@ -220,6 +221,15 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
no_cut = bug->flags & BUGFLAG_NO_CUT_HERE;
has_args = bug->flags & BUGFLAG_ARGS;

#ifdef CONFIG_KUNIT
/*
* Before the once logic so suppressed warnings do not consume
* the single-fire budget of WARN_ON_ONCE().
*/
if (warning && kunit_is_suppressed_warning(true))
return BUG_TRAP_TYPE_WARN;
#endif

if (warning && once) {
if (done)
return BUG_TRAP_TYPE_WARN;
Expand Down
4 changes: 3 additions & 1 deletion lib/kunit/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ kunit-objs += test.o \
executor.o \
attributes.o \
device.o \
platform.o
platform.o \
bug.o

ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o
Expand All @@ -21,6 +22,7 @@ obj-$(if $(CONFIG_KUNIT),y) += hooks.o

obj-$(CONFIG_KUNIT_TEST) += kunit-test.o
obj-$(CONFIG_KUNIT_TEST) += platform-test.o
obj-$(CONFIG_KUNIT_TEST) += backtrace-suppression-test.o

# string-stream-test compiles built-in only.
ifeq ($(CONFIG_KUNIT_TEST),y)
Expand Down
Loading
Loading