On Wed, May 6, 2026 at 11:40 AM David Gow <[email protected]> wrote:
>
> Le 04/05/2026 à 3:41 PM, Albert Esteve a écrit :
> > From: Alessandro Carminati <[email protected]>
> >
> > 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.
> >
> > Three 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_SUPPRESSED_WARNING(test): manual macros for larger
> >    blocks or when warning counts need to be checked after suppression
> >    ends. Limited to one pair per scope.
> > - kunit_start/end_suppress_warning(test): direct functions returning
> >    an explicit handle, for retaining the handle within the test,
> >    or for cross-function usage.
> >
> > Signed-off-by: Guenter Roeck <[email protected]>
> > Signed-off-by: Alessandro Carminati <[email protected]>
> > Reviewed-by: Kees Cook <[email protected]>
> > Signed-off-by: Albert Esteve <[email protected]>
> > ---
>
> This looks pretty good to me, thanks.
>
> Reviewed-by: David Gow <[email protected]>
>
> It's maybe slightly over-the-top to now have three different ways of
> enabling warning suppression: I'd probably personally get rid of
> KUNIT_START/END_SUPPRESSED_WARNING() if we had to lose one. But if
> there's a real reason to prefer keeping all three, it's not actually a
> problem to do so.

Thanks for the review!

I think the three forms earn their keep: the scoped form is the go-to
for most cases, but the macros avoid indentation without requiring
users to manage a raw pointer. I initially removed the macros and
added them back later. Direct calls to the functions will be less
frequent, used only when you need the handle.

That said, if it becomes a maintenance burden, the macros are the
easiest to drop since they're thin wrappers. Let me know if you prefer
them to be dropped, and I will send a v9 with that and the
`KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT` additions to the drm test
patch.

BR,
Albert.

>
> Regardless, this series is looking pretty ready to me. Let me know if
> you're planning a v9, otherwise we'll take this when you're ready.
>
> Cheers,
> -- David
>
> >   include/kunit/test-bug.h |  25 +++++++++
> >   include/kunit/test.h     | 138 
> > +++++++++++++++++++++++++++++++++++++++++++++++
> >   kernel/panic.c           |  15 +++++-
> >   lib/bug.c                |  10 ++++
> >   lib/kunit/Makefile       |   3 +-
> >   lib/kunit/bug.c          | 115 +++++++++++++++++++++++++++++++++++++++
> >   lib/kunit/hooks-impl.h   |   2 +
> >   7 files changed, 305 insertions(+), 3 deletions(-)
> >
> > diff --git a/include/kunit/test-bug.h b/include/kunit/test-bug.h
> > index 47aa8f21ccce8..6237e48ceadfd 100644
> > --- a/include/kunit/test-bug.h
> > +++ b/include/kunit/test-bug.h
> > @@ -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;
> >
> >   /**
> > @@ -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)
> >
> > diff --git a/include/kunit/test.h b/include/kunit/test.h
> > index 9cd1594ab697d..f278ec028019c 100644
> > --- a/include/kunit/test.h
> > +++ b/include/kunit/test.h
> > @@ -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 */
> > diff --git a/kernel/panic.c b/kernel/panic.c
> > index c78600212b6c1..697d8ca054bef 100644
> > --- a/kernel/panic.c
> > +++ b/kernel/panic.c
> > @@ -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
> > @@ -1080,9 +1081,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) {
> > @@ -1102,9 +1108,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);
> > diff --git a/lib/bug.c b/lib/bug.c
> > index 623c467a8b76c..a5cebde554ed8 100644
> > --- a/lib/bug.c
> > +++ b/lib/bug.c
> > @@ -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[];
> >
> > @@ -223,6 +224,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;
> > diff --git a/lib/kunit/Makefile b/lib/kunit/Makefile
> > index 656f1fa35abcc..4592f9d0aa8dd 100644
> > --- a/lib/kunit/Makefile
> > +++ b/lib/kunit/Makefile
> > @@ -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
> > diff --git a/lib/kunit/bug.c b/lib/kunit/bug.c
> > new file mode 100644
> > index 0000000000000..b0b6778d7399a
> > --- /dev/null
> > +++ b/lib/kunit/bug.c
> > @@ -0,0 +1,115 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * KUnit helpers for backtrace suppression
> > + *
> > + * Copyright (C) 2025 Alessandro Carminati <[email protected]>
> > + * Copyright (C) 2024 Guenter Roeck <[email protected]>
> > + */
> > +
> > +#include <kunit/resource.h>
> > +#include <linux/export.h>
> > +#include <linux/rculist.h>
> > +#include <linux/sched.h>
> > +#include <linux/spinlock.h>
> > +
> > +#include "hooks-impl.h"
> > +
> > +struct kunit_suppressed_warning {
> > +     struct list_head node;
> > +     struct task_struct *task;
> > +     struct kunit *test;
> > +     int counter;
> > +};
> > +
> > +static LIST_HEAD(suppressed_warnings);
> > +static DEFINE_SPINLOCK(suppressed_warnings_lock);
> > +
> > +static void kunit_suppress_warning_remove(struct kunit_suppressed_warning 
> > *w)
> > +{
> > +     unsigned long flags;
> > +
> > +     spin_lock_irqsave(&suppressed_warnings_lock, flags);
> > +     list_del_rcu(&w->node);
> > +     spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
> > +     synchronize_rcu(); /* Wait for readers to finish */
> > +}
> > +
> > +KUNIT_DEFINE_ACTION_WRAPPER(kunit_suppress_warning_cleanup,
> > +                         kunit_suppress_warning_remove,
> > +                         struct kunit_suppressed_warning *);
> > +
> > +bool kunit_has_active_suppress_warning(void)
> > +{
> > +     return __kunit_is_suppressed_warning_impl(false);
> > +}
> > +EXPORT_SYMBOL_GPL(kunit_has_active_suppress_warning);
> > +
> > +struct kunit_suppressed_warning *
> > +kunit_start_suppress_warning(struct kunit *test)
> > +{
> > +     struct kunit_suppressed_warning *w;
> > +     unsigned long flags;
> > +     int ret;
> > +
> > +     if (kunit_has_active_suppress_warning()) {
> > +             KUNIT_FAIL(test, "Another suppression block is already 
> > active");
> > +             return NULL;
> > +     }
> > +
> > +     w = kunit_kzalloc(test, sizeof(*w), GFP_KERNEL);
> > +     if (!w)
> > +             return NULL;
> > +
> > +     w->task = current;
> > +     w->test = test;
> > +
> > +     spin_lock_irqsave(&suppressed_warnings_lock, flags);
> > +     list_add_rcu(&w->node, &suppressed_warnings);
> > +     spin_unlock_irqrestore(&suppressed_warnings_lock, flags);
> > +
> > +     ret = kunit_add_action_or_reset(test,
> > +                                     kunit_suppress_warning_cleanup, w);
> > +     if (ret)
> > +             return NULL;
> > +
> > +     return w;
> > +}
> > +EXPORT_SYMBOL_GPL(kunit_start_suppress_warning);
> > +
> > +void kunit_end_suppress_warning(struct kunit *test,
> > +                             struct kunit_suppressed_warning *w)
> > +{
> > +     if (!w)
> > +             return;
> > +     kunit_release_action(test, kunit_suppress_warning_cleanup, w);
> > +}
> > +EXPORT_SYMBOL_GPL(kunit_end_suppress_warning);
> > +
> > +void __kunit_suppress_auto_cleanup(struct kunit_suppressed_warning **wp)
> > +{
> > +     if (*wp)
> > +             kunit_end_suppress_warning((*wp)->test, *wp);
> > +}
> > +EXPORT_SYMBOL_GPL(__kunit_suppress_auto_cleanup);
> > +
> > +int kunit_suppressed_warning_count(struct kunit_suppressed_warning *w)
> > +{
> > +     return w ? w->counter : 0;
> > +}
> > +EXPORT_SYMBOL_GPL(kunit_suppressed_warning_count);
> > +
> > +bool __kunit_is_suppressed_warning_impl(bool count)
> > +{
> > +     struct kunit_suppressed_warning *w;
> > +
> > +     guard(rcu)();
> > +     list_for_each_entry_rcu(w, &suppressed_warnings, node) {
> > +             if (w->task == current) {
> > +                     if (count)
> > +                             w->counter++;
> > +                     return true;
> > +             }
> > +     }
> > +
> > +     return false;
> > +}
> > diff --git a/lib/kunit/hooks-impl.h b/lib/kunit/hooks-impl.h
> > index 4e71b2d0143ba..d8720f2616925 100644
> > --- a/lib/kunit/hooks-impl.h
> > +++ b/lib/kunit/hooks-impl.h
> > @@ -19,6 +19,7 @@ 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);
> > +bool __kunit_is_suppressed_warning_impl(bool count);
> >
> >   /* Code to set all of the function pointers. */
> >   static inline void kunit_install_hooks(void)
> > @@ -26,6 +27,7 @@ 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;
> > +     kunit_hooks.is_suppressed_warning = 
> > __kunit_is_suppressed_warning_impl;
> >   }
> >
> >   #endif /* _KUNIT_HOOKS_IMPL_H */
> >
>


Reply via email to