On Mon, Jan 19, 2026 at 03:34:24PM +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.
> 
> Signed-off-by: David Gow <[email protected]>
> ---
>  Documentation/dev-tools/kunit/run_wrapper.rst |  3 ++
>  tools/testing/kunit/kunit.py                  | 25 +++++++++++-
>  tools/testing/kunit/kunit_junit.py            | 36 +++++++++++++++++
>  tools/testing/kunit/kunit_tool_test.py        | 40 +++++++++++++++++--
>  4 files changed, 100 insertions(+), 4 deletions(-)
>  create mode 100644 tools/testing/kunit/kunit_junit.py

(...)

> diff --git a/tools/testing/kunit/kunit.py b/tools/testing/kunit/kunit.py
> index e3d82a038f93..0698d27c3629 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
>  
> @@ -261,6 +263,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))
> +
>       if test.status != kunit_parser.TestStatus.SUCCESS:
>               return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
>  

(...)

> diff --git a/tools/testing/kunit/kunit_junit.py 
> b/tools/testing/kunit/kunit_junit.py
> new file mode 100644
> index 000000000000..58d482e0c793
> --- /dev/null
> +++ b/tools/testing/kunit/kunit_junit.py
> @@ -0,0 +1,36 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Generates JSON from KUnit results according to
> +# KernelCI spec: https://github.com/kernelci/kernelci-doc/wiki/Test-API
> +#
> +# Copyright (C) 2025, Google LLC.
> +# Author: David Gow <[email protected]>
> +
> +
> +from kunit_parser import Test, TestStatus
> +
> +def escape_xml_string(string : str) -> str:
> +     return string.replace("&", "&amp;").replace("\"", 
> "&quot;").replace("'", "&apos;").replace("<", "&lt;").replace(">", "&gt;")
> +
> +def get_test_suite(test: Test) -> str:
> +     xml_output = '<testsuite name="' + escape_xml_string(test.name) + '" 
> tests="' + str(test.counts.total()) + '" failures="' + 
> str(test.counts.failed) + '" skipped="' +str(test.counts.skipped) + '">\n'
> +
> +     for subtest in test.subtests:
> +             if subtest.subtests:
> +                     xml_output += get_test_suite(subtest)
> +                     continue
> +             xml_output += '<testcase name="' + 
> escape_xml_string(subtest.name) + '" >\n'
> +             if subtest.status == TestStatus.FAILURE:
> +                     xml_output += '<failure>Test Failed</failure>\n'
> +             xml_output += '<system-out><![CDATA[' + "\n".join(subtest.log) 
> + ']]></system-out>\n'
> +             xml_output += '</testcase>\n'
> +
> +     xml_output += '</testsuite>\n\n'
> +
> +     return xml_output
> +
> +def get_junit_result(test: Test) -> str:
> +     xml_output = '<?xml version="1.0" encoding="UTF-8" ?>\n\n'
> +
> +     xml_output += get_test_suite(test)
> +     return xml_output

Did you look into the Python stdlib XML serializer?
https://docs.python.org/3/library/xml.sax.utils.html#xml.sax.saxutils.XMLGenerator
https://docs.python.org/3/library/xml.sax.handler.html#contenthandler-objects

With that there should be no need to mess around with low-level XML syntax.

(...)


Thomas

Reply via email to