Hi David,

On Sat, May 02, 2026 at 10:49:17AM +0800, David Gow wrote:
> This is used by things like Jenkins and other CI systems, which can
> pretty-print the test output and potentially provide test-level comparisons
> between runs.
> 
> The implementation here is pretty basic: it only provides the raw results,
> split into tests and test suites, and doesn't provide any overall metadata.
> However, CI systems like Jenkins can injest it and it is already useful.

"ingest"?

> Signed-off-by: David Gow <[email protected]>
> ---
> 
> Finally got around to doing a new version of this. I'm running this
> locally with Jenkins, and it's giving nice summaries of test results.
> 
> Changes since v1:
> https://lore.kernel.org/all/[email protected]/
> - Use python's provided XML quote escaping, rather than coding our own 
> (Thanks Thomas)

What I tried to suggest was to do all of the XML generation through the Python
module instead of manual string concatenation. The XML escaping would go away
as a side effect.

A pseudo-code example:

out = xml.sax.saxutils.XMLGenerator(f, encoding='utf-8')
out.startDocument()

for subtest in test.subtests:
    out.startElement('testcase', {'name': 'subtest.name' })

    if subtest.status == TestStatus.FAILURE:
        out.startElement('failure', {})
        out.characters('Test failed')
        out.endElement('failure')

    out.endElement('testcase')

How I used it recently:
https://github.com/Linutronix/elbe/blob/master/website/ext/elbedocoverview.py#L133

> - Output proper <skipped> tags for skipped tests
> - Report crashed tests as <error>
> - Don't output <system-out> tags if there are no lines of log data
> 
> ---
>  Documentation/dev-tools/kunit/run_wrapper.rst |  3 ++
>  tools/testing/kunit/kunit.py                  | 25 ++++++++++-
>  tools/testing/kunit/kunit_junit.py            | 43 +++++++++++++++++++
>  tools/testing/kunit/kunit_tool_test.py        | 38 ++++++++++++++--
>  4 files changed, 105 insertions(+), 4 deletions(-)
>  create mode 100644 tools/testing/kunit/kunit_junit.py
> 
> diff --git a/Documentation/dev-tools/kunit/run_wrapper.rst 
> b/Documentation/dev-tools/kunit/run_wrapper.rst
> index 770bb09a475a..cecc110a3399 100644
> --- a/Documentation/dev-tools/kunit/run_wrapper.rst
> +++ b/Documentation/dev-tools/kunit/run_wrapper.rst
> @@ -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"``.
> diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
> index 742f5c555666..1a7ff594b791 100755
> --- a/tools/testing/kunit/kunit.py
> +++ b/tools/testing/kunit/kunit.py
> @@ -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,17 @@ 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:
> +             junit_str = kunit_junit.get_junit_result(
> +                                     test=test)

Unnecessary linebreak?

> +             if request.junit == 'stdout':
> +                     print(junit_str)
> +             else:
> +                     with open(request.junit, 'w') as f:
> +                             f.write(junit_str)
> +                     stdout.print_with_timestamp("Test results stored in %s" 
> %
> +                             os.path.abspath(request.junit))

f-string?

> +
>       if test.status != kunit_parser.TestStatus.SUCCESS:
>               return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
>  
> @@ -309,6 +322,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',
>  }

(...)

But all of it are just suggestions.


Thomas

Reply via email to