mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 01:08:13 -04:00
Merge tag 'linux_kselftest-kunit-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest
Pull kunit updates from Shuah Khan: "Fixes to tool and kunit core and new features to both to support JUnit XML (primitive) and backtrace suppression API: - Core support for suppressing warning backtraces - Parse and print the reason tests are skipped - Add (primitive) support for outputting JUnit XML - Don't write to stdout when it should be disabled - Add backtrace suppression self-tests - Suppress intentional warning backtraces in scaling unit tests - Add documentation for warning backtrace suppression API - Fix spelling mistakes in comments and messages - gen_compile_commands: Ignore libgcc.a - qemu_configs: Add or1k / openrisc configuration" * tag 'linux_kselftest-kunit-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: kunit:tool: Don't write to stdout when it should be disabled kunit: tool: Add (primitive) support for outputting JUnit XML kunit: tool: Parse and print the reason tests are skipped kunit: Add documentation for warning backtrace suppression API drm: Suppress intentional warning backtraces in scaling unit tests kunit: Add backtrace suppression self-tests bug/kunit: Core support for suppressing warning backtraces kunit: Fix spelling mistakes in comments and messages kunit: qemu_configs: Add or1k / openrisc configuration gen_compile_commands: Ignore libgcc.a
This commit is contained in:
@@ -324,6 +324,9 @@ command line arguments:
|
||||
- ``--json``: If set, stores the test results in a JSON format and prints to `stdout` or
|
||||
saves to a file if a filename is specified.
|
||||
|
||||
- ``--junit``: If set, stores the test results in JUnit XML format and prints to `stdout` or
|
||||
saves to a file if a filename is specified.
|
||||
|
||||
- ``--filter``: Specifies filters on test attributes, for example, ``speed!=slow``.
|
||||
Multiple filters can be used by wrapping input in quotes and separating filters
|
||||
by commas. Example: ``--filter "speed>slow, module=example"``.
|
||||
|
||||
@@ -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
|
||||
~~~~~~~~~~~
|
||||
@@ -1211,4 +1255,4 @@ For example:
|
||||
dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");
|
||||
|
||||
// Everything is cleaned up automatically when the test ends.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -407,10 +408,27 @@ 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;
|
||||
int expected_warnings = params->expected_scaling_factor == -EINVAL;
|
||||
int scaling_factor = INT_MIN;
|
||||
|
||||
scaling_factor = drm_rect_calc_hscale(¶ms->src, ¶ms->dst,
|
||||
params->min_range, params->max_range);
|
||||
/*
|
||||
* Without CONFIG_BUG, WARN_ON() is a no-op and the suppressed warning
|
||||
* count stays zero, failing the assertion.
|
||||
*/
|
||||
if (expected_warnings && !IS_ENABLED(CONFIG_BUG))
|
||||
kunit_skip(test, "requires CONFIG_BUG");
|
||||
|
||||
/*
|
||||
* 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(¶ms->src, ¶ms->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);
|
||||
}
|
||||
@@ -418,10 +436,26 @@ static void drm_test_rect_calc_hscale(struct kunit *test)
|
||||
static void drm_test_rect_calc_vscale(struct kunit *test)
|
||||
{
|
||||
const struct drm_rect_scale_case *params = test->param_value;
|
||||
int scaling_factor;
|
||||
int expected_warnings = params->expected_scaling_factor == -EINVAL;
|
||||
int scaling_factor = INT_MIN;
|
||||
|
||||
scaling_factor = drm_rect_calc_vscale(¶ms->src, ¶ms->dst,
|
||||
params->min_range, params->max_range);
|
||||
/*
|
||||
* Without CONFIG_BUG, WARN_ON() is a no-op and the suppressed warning
|
||||
* count stays zero, failing the assertion.
|
||||
*/
|
||||
if (expected_warnings && !IS_ENABLED(CONFIG_BUG))
|
||||
kunit_skip(test, "requires CONFIG_BUG");
|
||||
|
||||
/*
|
||||
* 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(¶ms->src, ¶ms->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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1796,4 +1796,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 */
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
|
||||
14
lib/bug.c
14
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[];
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -220,6 +219,17 @@ 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
|
||||
|
||||
disable_trace_on_warning();
|
||||
|
||||
if (warning && once) {
|
||||
if (done)
|
||||
return BUG_TRAP_TYPE_WARN;
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
198
lib/kunit/backtrace-suppression-test.c
Normal file
198
lib/kunit/backtrace-suppression-test.c
Normal file
@@ -0,0 +1,198 @@
|
||||
// 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");
|
||||
120
lib/kunit/bug.c
Normal file
120
lib/kunit/bug.c
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* KUnit helpers for backtrace suppression
|
||||
*
|
||||
* Copyright (C) 2025 Alessandro Carminati <acarmina@redhat.com>
|
||||
* Copyright (C) 2024 Guenter Roeck <linux@roeck-us.net>
|
||||
*/
|
||||
|
||||
#include <kunit/resource.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include "hooks-impl.h"
|
||||
|
||||
struct kunit_suppressed_warning {
|
||||
struct list_head node;
|
||||
struct task_struct *task;
|
||||
struct kunit *test;
|
||||
atomic_t 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);
|
||||
put_task_struct(w->task);
|
||||
}
|
||||
|
||||
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) {
|
||||
KUNIT_FAIL(test, "Failed to allocate suppression handle.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
w->task = get_task_struct(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) {
|
||||
KUNIT_FAIL(test, "Failed to add suppression cleanup action.");
|
||||
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 ? atomic_read(&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)
|
||||
atomic_inc(&w->counter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
@@ -201,6 +201,8 @@ def main():
|
||||
# Modules are listed in modules.order.
|
||||
if os.path.isdir(path):
|
||||
cmdfiles = cmdfiles_in_dir(path)
|
||||
elif os.path.basename(path) == 'libgcc.a':
|
||||
cmdfiles = []
|
||||
elif path.endswith('.a'):
|
||||
cmdfiles = cmdfiles_for_a(path, ar)
|
||||
elif path.endswith('modules.order'):
|
||||
|
||||
@@ -21,6 +21,7 @@ from enum import Enum, auto
|
||||
from typing import Iterable, List, Optional, Sequence, Tuple
|
||||
|
||||
import kunit_json
|
||||
import kunit_junit
|
||||
import kunit_kernel
|
||||
import kunit_parser
|
||||
from kunit_printer import stdout, null_printer
|
||||
@@ -49,6 +50,7 @@ class KunitBuildRequest(KunitConfigRequest):
|
||||
class KunitParseRequest:
|
||||
raw_output: Optional[str]
|
||||
json: Optional[str]
|
||||
junit: Optional[str]
|
||||
summary: bool
|
||||
failed: bool
|
||||
|
||||
@@ -268,6 +270,13 @@ def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input
|
||||
stdout.print_with_timestamp("Test results stored in %s" %
|
||||
os.path.abspath(request.json))
|
||||
|
||||
if request.junit:
|
||||
if request.junit == 'stdout':
|
||||
kunit_junit.print_junit_result(test=test)
|
||||
else:
|
||||
kunit_junit.write_junit_result(test=test,filename=request.junit)
|
||||
stdout.print_with_timestamp(f"Test results stored in {os.path.abspath(request.junit)}")
|
||||
|
||||
if test.status != kunit_parser.TestStatus.SUCCESS:
|
||||
return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
|
||||
|
||||
@@ -309,6 +318,7 @@ def run_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
# So we hackily automatically rewrite --json => --json=stdout
|
||||
pseudo_bool_flag_defaults = {
|
||||
'--json': 'stdout',
|
||||
'--junit': 'stdout',
|
||||
'--raw_output': 'kunit',
|
||||
}
|
||||
def massage_argv(argv: Sequence[str]) -> Sequence[str]:
|
||||
@@ -459,6 +469,11 @@ def add_parse_opts(parser: argparse.ArgumentParser) -> None:
|
||||
help='Prints parsed test results as JSON to stdout or a file if '
|
||||
'a filename is specified. Does nothing if --raw_output is set.',
|
||||
type=str, const='stdout', default=None, metavar='FILE')
|
||||
parser.add_argument('--junit',
|
||||
nargs='?',
|
||||
help='Prints parsed test results as JUnit XML to stdout or a file if '
|
||||
'a filename is specified. Does nothing if --raw_output is set.',
|
||||
type=str, const='stdout', default=None, metavar='FILE')
|
||||
parser.add_argument('--summary',
|
||||
help='Prints only the summary line for parsed test results.'
|
||||
'Does nothing if --raw_output is set.',
|
||||
@@ -502,6 +517,7 @@ def run_handler(cli_args: argparse.Namespace) -> None:
|
||||
jobs=cli_args.jobs,
|
||||
raw_output=cli_args.raw_output,
|
||||
json=cli_args.json,
|
||||
junit=cli_args.junit,
|
||||
summary=cli_args.summary,
|
||||
failed=cli_args.failed,
|
||||
timeout=cli_args.timeout,
|
||||
@@ -552,6 +568,7 @@ def exec_handler(cli_args: argparse.Namespace) -> None:
|
||||
exec_request = KunitExecRequest(raw_output=cli_args.raw_output,
|
||||
build_dir=cli_args.build_dir,
|
||||
json=cli_args.json,
|
||||
junit=cli_args.junit,
|
||||
summary=cli_args.summary,
|
||||
failed=cli_args.failed,
|
||||
timeout=cli_args.timeout,
|
||||
@@ -580,7 +597,9 @@ def parse_handler(cli_args: argparse.Namespace) -> None:
|
||||
# We know nothing about how the result was created!
|
||||
metadata = kunit_json.Metadata()
|
||||
request = KunitParseRequest(raw_output=cli_args.raw_output,
|
||||
json=cli_args.json, summary=cli_args.summary,
|
||||
json=cli_args.json,
|
||||
junit=cli_args.junit,
|
||||
summary=cli_args.summary,
|
||||
failed=cli_args.failed)
|
||||
result, _ = parse_tests(request, metadata, kunit_output)
|
||||
if result.status != KunitStatus.SUCCESS:
|
||||
|
||||
61
tools/testing/kunit/kunit_junit.py
Normal file
61
tools/testing/kunit/kunit_junit.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Generates JUnit XML files from KUnit test results
|
||||
#
|
||||
# Copyright (C) 2026, Google LLC and David Gow.
|
||||
|
||||
from xml.sax.saxutils import quoteattr, XMLGenerator
|
||||
import xml.etree.ElementTree as ET
|
||||
from kunit_parser import Test, TestStatus
|
||||
from typing import Optional
|
||||
|
||||
# Get a string representing a tes suite (including subtests) in JUnit XML
|
||||
def get_test_suite(test: Test, parent: Optional[ET.Element]) -> ET.Element:
|
||||
suite_attrs = {
|
||||
'name': test.name,
|
||||
'tests': str(test.counts.total()),
|
||||
'failures': str(test.counts.failed),
|
||||
'skipped': str(test.counts.skipped),
|
||||
'errors': str(test.counts.crashed + test.counts.errors),
|
||||
}
|
||||
|
||||
if parent is not None:
|
||||
test_suite_element = ET.SubElement(parent, 'testsuite', suite_attrs)
|
||||
else:
|
||||
test_suite_element = ET.Element('testsuite', suite_attrs)
|
||||
|
||||
for subtest in test.subtests:
|
||||
if subtest.subtests:
|
||||
get_test_suite(subtest, test_suite_element)
|
||||
continue
|
||||
test_case_element = ET.SubElement(test_suite_element, 'testcase', {'name': subtest.name})
|
||||
if subtest.status == TestStatus.FAILURE:
|
||||
ET.SubElement(test_case_element, 'failure', {}).text = 'Test Failed'
|
||||
elif subtest.status == TestStatus.SKIPPED:
|
||||
ET.SubElement(test_case_element, 'skipped', {}).text = subtest.skip_reason
|
||||
elif subtest.status == TestStatus.TEST_CRASHED:
|
||||
ET.SubElement(test_case_element, 'error', {}).text = 'Test Crashed'
|
||||
|
||||
if subtest.log:
|
||||
ET.SubElement(test_case_element, 'system-out', {}).text = "\n".join(subtest.log)
|
||||
|
||||
return test_suite_element
|
||||
|
||||
# Get a string for an entire XML file for the test structure starting at test
|
||||
def get_junit_result(test: Test) -> str:
|
||||
root_element = get_test_suite(test, None)
|
||||
ET.indent(root_element)
|
||||
return ET.tostring(root_element, encoding="unicode", xml_declaration=True)
|
||||
|
||||
# Print a JUnit result to stdout.
|
||||
def print_junit_result(test: Test) -> None:
|
||||
root_element = get_test_suite(test, None)
|
||||
ET.indent(root_element)
|
||||
ET.dump(root_element)
|
||||
|
||||
# Write an entire XML file for the test structure starting at test
|
||||
def write_junit_result(test: Test, filename: str) -> None:
|
||||
root_element = get_test_suite(test, None)
|
||||
ET.indent(root_element)
|
||||
root_et = ET.ElementTree(root_element)
|
||||
root_et.write(filename, encoding='utf-8', xml_declaration=True)
|
||||
@@ -218,7 +218,7 @@ def _get_qemu_ops(config_path: str,
|
||||
# exists (I learned this through experimentation and could not find it
|
||||
# anywhere in the Python documentation).
|
||||
#
|
||||
# Bascially, we completely ignore the actual file location of the config
|
||||
# Basically, we completely ignore the actual file location of the config
|
||||
# we are loading and just tell Python that the module lives in the
|
||||
# QEMU_CONFIGS_DIR for import purposes regardless of where it actually
|
||||
# exists as a file.
|
||||
|
||||
@@ -17,7 +17,7 @@ import textwrap
|
||||
from enum import Enum, auto
|
||||
from typing import Iterable, Iterator, List, Optional, Tuple
|
||||
|
||||
from kunit_printer import Printer, stdout
|
||||
from kunit_printer import Printer
|
||||
|
||||
class Test:
|
||||
"""
|
||||
@@ -44,11 +44,12 @@ class Test:
|
||||
self.subtests = [] # type: List[Test]
|
||||
self.log = [] # type: List[str]
|
||||
self.counts = TestCounts()
|
||||
self.skip_reason = ''
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Returns string representation of a Test class object."""
|
||||
return (f'Test({self.status}, {self.name}, {self.expected_count}, '
|
||||
f'{self.subtests}, {self.log}, {self.counts})')
|
||||
f'{self.subtests}, {self.log}, {self.counts}, {self.skip_reason})')
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Returns string representation of a Test class object."""
|
||||
@@ -57,7 +58,7 @@ class Test:
|
||||
def add_error(self, printer: Printer, error_message: str) -> None:
|
||||
"""Records an error that occurred while parsing this test."""
|
||||
self.counts.errors += 1
|
||||
printer.print_with_timestamp(stdout.red('[ERROR]') + f' Test: {self.name}: {error_message}')
|
||||
printer.print_with_timestamp(printer.red('[ERROR]') + f' Test: {self.name}: {error_message}')
|
||||
|
||||
def ok_status(self) -> bool:
|
||||
"""Returns true if the status was ok, i.e. passed or skipped."""
|
||||
@@ -268,7 +269,7 @@ def check_version(version_num: int, accepted_versions: List[int],
|
||||
if version_num < min(accepted_versions):
|
||||
test.add_error(printer, f'{version_type} version lower than expected!')
|
||||
elif version_num > max(accepted_versions):
|
||||
test.add_error(printer, f'{version_type} version higer than expected!')
|
||||
test.add_error(printer, f'{version_type} version higher than expected!')
|
||||
|
||||
def parse_ktap_header(lines: LineStream, test: Test, printer: Printer) -> bool:
|
||||
"""
|
||||
@@ -352,9 +353,9 @@ def parse_test_plan(lines: LineStream, test: Test) -> bool:
|
||||
lines.pop()
|
||||
return True
|
||||
|
||||
TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(- )?([^#]*)( # .*)?$')
|
||||
TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(:?- )?([^#]*)( # .*)?$')
|
||||
|
||||
TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(- )?(.*) # SKIP ?(.*)$')
|
||||
TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) ?(:?- )?(.*) # SKIP ?(.*)$')
|
||||
|
||||
def peek_test_name_match(lines: LineStream, test: Test) -> bool:
|
||||
"""
|
||||
@@ -418,7 +419,7 @@ def parse_test_result(lines: LineStream, test: Test,
|
||||
|
||||
# Set name of test object
|
||||
if skip_match:
|
||||
test.name = skip_match.group(4) or skip_match.group(5)
|
||||
test.name = skip_match.group(4)
|
||||
else:
|
||||
test.name = match.group(4)
|
||||
|
||||
@@ -431,6 +432,7 @@ def parse_test_result(lines: LineStream, test: Test,
|
||||
status = match.group(1)
|
||||
if skip_match:
|
||||
test.status = TestStatus.SKIPPED
|
||||
test.skip_reason = skip_match.group(5) or ''
|
||||
elif status == 'ok':
|
||||
test.status = TestStatus.SUCCESS
|
||||
else:
|
||||
@@ -539,12 +541,15 @@ def format_test_result(test: Test, printer: Printer) -> str:
|
||||
if test.status == TestStatus.SUCCESS:
|
||||
return printer.green('[PASSED] ') + test.name
|
||||
if test.status == TestStatus.SKIPPED:
|
||||
return printer.yellow('[SKIPPED] ') + test.name
|
||||
skip_message = printer.yellow('[SKIPPED] ') + test.name
|
||||
if test.skip_reason != '':
|
||||
skip_message += printer.yellow(' (' + test.skip_reason + ')')
|
||||
return skip_message
|
||||
if test.status == TestStatus.NO_TESTS:
|
||||
return printer.yellow('[NO TESTS RUN] ') + test.name
|
||||
if test.status == TestStatus.TEST_CRASHED:
|
||||
print_log(test.log, printer)
|
||||
return stdout.red('[CRASHED] ') + test.name
|
||||
return printer.red('[CRASHED] ') + test.name
|
||||
print_log(test.log, printer)
|
||||
return printer.red('[FAILED] ') + test.name
|
||||
|
||||
@@ -651,11 +656,11 @@ def print_summary_line(test: Test, printer: Printer) -> None:
|
||||
printer - Printer object to output results
|
||||
"""
|
||||
if test.status == TestStatus.SUCCESS:
|
||||
color = stdout.green
|
||||
color = printer.green
|
||||
elif test.status in (TestStatus.SKIPPED, TestStatus.NO_TESTS):
|
||||
color = stdout.yellow
|
||||
color = printer.yellow
|
||||
else:
|
||||
color = stdout.red
|
||||
color = printer.red
|
||||
printer.print_with_timestamp(color(f'Testing complete. {test.counts}'))
|
||||
|
||||
# Summarize failures that might have gone off-screen since we had a lot
|
||||
|
||||
@@ -24,6 +24,7 @@ import kunit_config
|
||||
import kunit_parser
|
||||
import kunit_kernel
|
||||
import kunit_json
|
||||
import kunit_junit
|
||||
import kunit
|
||||
from kunit_printer import stdout
|
||||
|
||||
@@ -235,10 +236,27 @@ class KUnitParserTest(unittest.TestCase):
|
||||
with open(skipped_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
|
||||
|
||||
# The test result is skipped, and the skip reason is valid
|
||||
self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[1].subtests[1].status)
|
||||
self.assertEqual("this test should be skipped", result.subtests[1].subtests[1].skip_reason)
|
||||
|
||||
# A skipped test does not fail the whole suite.
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=4, skipped=1))
|
||||
|
||||
def test_skipped_reason_parse(self):
|
||||
skipped_log = _test_data_path('test_skip_all_tests.log')
|
||||
with open(skipped_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines(), stdout)
|
||||
|
||||
# The first test is skipped, with the correct reaons
|
||||
self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[0].subtests[0].status)
|
||||
self.assertEqual("all tests skipped", result.subtests[0].subtests[0].skip_reason)
|
||||
|
||||
# The first suite is skipped, with no reason
|
||||
self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.subtests[0].status)
|
||||
self.assertEqual("", result.subtests[0].skip_reason)
|
||||
|
||||
def test_skipped_all_tests(self):
|
||||
skipped_log = _test_data_path('test_skip_all_tests.log')
|
||||
with open(skipped_log) as file:
|
||||
@@ -676,6 +694,38 @@ class StrContains(str):
|
||||
def __eq__(self, other):
|
||||
return self in other
|
||||
|
||||
class KUnitJUnitTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.print_mock = mock.patch('kunit_printer.Printer.print').start()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _junit_string(self, log_file):
|
||||
with open(_test_data_path(log_file)) as file:
|
||||
test_result = kunit_parser.parse_run_tests(file, stdout)
|
||||
junit_string = kunit_junit.get_junit_result(
|
||||
test=test_result)
|
||||
print(junit_string)
|
||||
return junit_string
|
||||
|
||||
def test_failed_test_junit(self):
|
||||
result = self._junit_string('test_is_test_passed-failure.log')
|
||||
self.assertTrue("<failure>" in result)
|
||||
|
||||
def test_skipped_test_junit(self):
|
||||
result = self._junit_string('test_skip_tests.log')
|
||||
self.assertTrue("<skipped>" in result)
|
||||
self.assertTrue("skipped=\"1\"" in result)
|
||||
|
||||
def test_crashed_test_junit(self):
|
||||
result = self._junit_string('test_kernel_panic_interrupt.log')
|
||||
self.assertTrue("<error>" in result);
|
||||
|
||||
def test_no_tests_junit(self):
|
||||
result = self._junit_string('test_is_test_passed-no_tests_run_with_header.log')
|
||||
self.assertTrue("tests=\"0\"" in result)
|
||||
self.assertFalse("testcase" in result)
|
||||
|
||||
|
||||
class KUnitMainTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
path = _test_data_path('test_is_test_passed-all_passed.log')
|
||||
@@ -923,7 +973,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
self.linux_source_mock.run_kernel.return_value = ['TAP version 14', 'init: random output'] + want
|
||||
|
||||
got = kunit._list_tests(self.linux_source_mock,
|
||||
kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
|
||||
kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'suite', False, False, False))
|
||||
self.assertEqual(got, want)
|
||||
# Should respect the user's filter glob when listing tests.
|
||||
self.linux_source_mock.run_kernel.assert_called_once_with(
|
||||
@@ -936,7 +986,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
|
||||
# Should respect the user's filter glob when listing tests.
|
||||
mock_tests.assert_called_once_with(mock.ANY,
|
||||
kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
|
||||
kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*.test*', '', None, None, 'suite', False, False, False))
|
||||
self.linux_source_mock.run_kernel.assert_has_calls([
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test*', filter='', filter_action=None, timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite2.test*', filter='', filter_action=None, timeout=300),
|
||||
@@ -949,7 +999,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
|
||||
# Should respect the user's filter glob when listing tests.
|
||||
mock_tests.assert_called_once_with(mock.ANY,
|
||||
kunit.KunitExecRequest(None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
|
||||
kunit.KunitExecRequest(None, None, None, False, False, '.kunit', 300, 'suite*', '', None, None, 'test', False, False, False))
|
||||
self.linux_source_mock.run_kernel.assert_has_calls([
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test1', filter='', filter_action=None, timeout=300),
|
||||
mock.call(args=None, build_dir='.kunit', filter_glob='suite.test2', filter='', filter_action=None, timeout=300),
|
||||
|
||||
18
tools/testing/kunit/qemu_configs/or1k.py
Normal file
18
tools/testing/kunit/qemu_configs/or1k.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
from ..qemu_config import QemuArchParams
|
||||
|
||||
QEMU_ARCH = QemuArchParams(linux_arch='openrisc',
|
||||
kconfig='''
|
||||
CONFIG_SERIAL_8250=y
|
||||
CONFIG_SERIAL_8250_CONSOLE=y
|
||||
CONFIG_SERIAL_OF_PLATFORM=y
|
||||
CONFIG_POWER_RESET=y
|
||||
CONFIG_POWER_RESET_SYSCON=y
|
||||
''',
|
||||
qemu_arch='or1k',
|
||||
kernel_path='vmlinux',
|
||||
kernel_command_line='console=ttyS0',
|
||||
extra_qemu_params=[
|
||||
'-machine', 'virt',
|
||||
'-m', '512',
|
||||
])
|
||||
Reference in New Issue
Block a user