Skip to content
Open
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
46 changes: 45 additions & 1 deletion Documentation/dev-tools/kunit/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,50 @@ 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.
Two API forms are available.

- 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.

- 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 +1255,4 @@ For example:
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");

// Everything is cleaned up automatically when the test ends.
}
}
36 changes: 30 additions & 6 deletions drivers/gpu/drm/tests/drm_rect_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <drm/drm_rect.h>
#include <drm/drm_mode.h>

#include <linux/limits.h>
#include <linux/string_helpers.h>
#include <linux/errno.h>

Expand Down Expand Up @@ -407,21 +408,44 @@ KUNIT_ARRAY_PARAM(drm_rect_scale, drm_rect_scale_cases, drm_rect_scale_case_desc
static void drm_test_rect_calc_hscale(struct kunit *test)
{
const struct drm_rect_scale_case *params = test->param_value;
int scaling_factor;
/* With CONFIG_BUG=n, WARN_ON() is a no-op so no warning fires. */
int expected_warnings = IS_ENABLED(CONFIG_BUG) ?
(params->expected_scaling_factor == -EINVAL) : 0;
int scaling_factor = INT_MIN;

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 unit tests with -EINVAL
* 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_SUPPRESSED_WARNING_COUNT(test, expected_warnings);
}

KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor);
}

static void drm_test_rect_calc_vscale(struct kunit *test)
{
const struct drm_rect_scale_case *params = test->param_value;
int scaling_factor;
/* With CONFIG_BUG=n, WARN_ON() is a no-op so no warning fires. */
int expected_warnings = IS_ENABLED(CONFIG_BUG) ?
(params->expected_scaling_factor == -EINVAL) : 0;
int scaling_factor = INT_MIN;

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 unit tests with -EINVAL
* 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_SUPPRESSED_WARNING_COUNT(test, expected_warnings);
}

KUNIT_EXPECT_EQ(test, scaling_factor, params->expected_scaling_factor);
}
Expand Down
26 changes: 26 additions & 0 deletions include/kunit/test-bug.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define _KUNIT_TEST_BUG_H

#include <linux/stddef.h> /* for NULL */
#include <linux/types.h> /* for bool */

#if IS_ENABLED(CONFIG_KUNIT)

Expand All @@ -23,6 +24,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 +62,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
98 changes: 98 additions & 0 deletions include/kunit/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -1795,4 +1795,102 @@ 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. Two forms
* are provided:
*
* - 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.
*
* - 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_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.
*/
#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 */
11 changes: 11 additions & 0 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 @@ -1124,6 +1125,11 @@ void warn_slowpath_fmt(const char *file, int line, unsigned taint,
bool rcu = warn_rcu_enter();
struct warn_args args;

if (kunit_is_suppressed_warning(true)) {
warn_rcu_exit(rcu);
return;
}

pr_warn(CUT_HERE);

if (!fmt) {
Expand All @@ -1146,6 +1152,11 @@ void __warn_printk(const char *fmt, ...)
bool rcu = warn_rcu_enter();
va_list args;

if (kunit_is_suppressed_warning(false)) {
warn_rcu_exit(rcu);
return;
}

pr_warn(CUT_HERE);

va_start(args, fmt);
Expand Down
12 changes: 10 additions & 2 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 @@ -209,8 +210,6 @@ static enum bug_trap_type __report_bug(struct bug_entry *bug, unsigned long buga
return BUG_TRAP_TYPE_NONE;
}

disable_trace_on_warning();

bug_get_file_line(bug, &file, &line);
fmt = bug_get_format(bug);

Expand All @@ -220,6 +219,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;

/*
* 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;

disable_trace_on_warning();

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