root/lib/kunit/backtrace-suppression-test.c
// SPDX-License-Identifier: GPL-2.0
/*
 * KUnit test for suppressing warning tracebacks.
 *
 * Copyright (C) 2024, Guenter Roeck
 * Author: Guenter Roeck <linux@roeck-us.net>
 */

#include <kunit/test.h>
#include <linux/bug.h>
#include <linux/completion.h>
#include <linux/kthread.h>

static void backtrace_suppression_test_warn_direct(struct kunit *test)
{
        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");

        kunit_warning_suppress(test) {
                WARN(1, "This backtrace should be suppressed");
                /*
                 * Count must be checked inside the scope; the handle
                 * is not accessible after the block exits.
                 */
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
        }
        KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
}

static noinline void trigger_backtrace_warn(void)
{
        WARN(1, "This backtrace should be suppressed");
}

static void backtrace_suppression_test_warn_indirect(struct kunit *test)
{
        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");

        kunit_warning_suppress(test) {
                trigger_backtrace_warn();
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
        }
}

static void backtrace_suppression_test_warn_multi(struct kunit *test)
{
        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");

        kunit_warning_suppress(test) {
                WARN(1, "This backtrace should be suppressed");
                trigger_backtrace_warn();
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2);
        }
}

static void backtrace_suppression_test_warn_on_direct(struct kunit *test)
{
        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");
        if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE) && !IS_ENABLED(CONFIG_KALLSYMS))
                kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE or CONFIG_KALLSYMS");

        kunit_warning_suppress(test) {
                WARN_ON(1);
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
        }
}

static noinline void trigger_backtrace_warn_on(void)
{
        WARN_ON(1);
}

static void backtrace_suppression_test_warn_on_indirect(struct kunit *test)
{
        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");
        if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE))
                kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE");

        kunit_warning_suppress(test) {
                trigger_backtrace_warn_on();
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);
        }
}

static void backtrace_suppression_test_count(struct kunit *test)
{
        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");

        kunit_warning_suppress(test) {
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 0);

                WARN(1, "suppressed");
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1);

                WARN(1, "suppressed again");
                KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 2);
        }
}

static void backtrace_suppression_test_active_state(struct kunit *test)
{
        KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());

        kunit_warning_suppress(test) {
                KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning());
        }

        KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());

        kunit_warning_suppress(test) {
                KUNIT_EXPECT_TRUE(test, kunit_has_active_suppress_warning());
        }

        KUNIT_EXPECT_FALSE(test, kunit_has_active_suppress_warning());
}

static void backtrace_suppression_test_multi_scope(struct kunit *test)
{
        struct kunit_suppressed_warning *sw1, *sw2;

        if (!IS_ENABLED(CONFIG_BUG))
                kunit_skip(test, "requires CONFIG_BUG");
        if (!IS_ENABLED(CONFIG_DEBUG_BUGVERBOSE))
                kunit_skip(test, "requires CONFIG_DEBUG_BUGVERBOSE");

        sw1 = kunit_start_suppress_warning(test);
        trigger_backtrace_warn_on();
        WARN(1, "suppressed by sw1");
        kunit_end_suppress_warning(test, sw1);

        sw2 = kunit_start_suppress_warning(test);
        WARN(1, "suppressed by sw2");
        kunit_end_suppress_warning(test, sw2);

        KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw1), 2);
        KUNIT_EXPECT_EQ(test, kunit_suppressed_warning_count(sw2), 1);
}

struct cross_kthread_data {
        bool was_active;
        struct completion done;
};

static int cross_kthread_fn(void *data)
{
        struct cross_kthread_data *d = data;

        d->was_active = kunit_has_active_suppress_warning();
        complete(&d->done);
        while (!kthread_should_stop())
                schedule();
        return 0;
}

static void backtrace_suppression_test_cross_kthread(struct kunit *test)
{
        struct cross_kthread_data data;
        struct task_struct *task;

        data.was_active = false;
        init_completion(&data.done);

        kunit_warning_suppress(test) {
                task = kthread_run(cross_kthread_fn, &data, "kunit-cross-test");
                KUNIT_ASSERT_FALSE(test, IS_ERR(task));
                wait_for_completion(&data.done);
                kthread_stop(task);
        }

        KUNIT_EXPECT_FALSE(test, data.was_active);
}

static struct kunit_case backtrace_suppression_test_cases[] = {
        KUNIT_CASE(backtrace_suppression_test_warn_direct),
        KUNIT_CASE(backtrace_suppression_test_warn_indirect),
        KUNIT_CASE(backtrace_suppression_test_warn_multi),
        KUNIT_CASE(backtrace_suppression_test_warn_on_direct),
        KUNIT_CASE(backtrace_suppression_test_warn_on_indirect),
        KUNIT_CASE(backtrace_suppression_test_count),
        KUNIT_CASE(backtrace_suppression_test_active_state),
        KUNIT_CASE(backtrace_suppression_test_multi_scope),
        KUNIT_CASE(backtrace_suppression_test_cross_kthread),
        {}
};

static struct kunit_suite backtrace_suppression_test_suite = {
        .name = "backtrace-suppression-test",
        .test_cases = backtrace_suppression_test_cases,
};
kunit_test_suites(&backtrace_suppression_test_suite);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("KUnit test to verify warning backtrace suppression");