Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

kunit: tool: make parser preserve whitespace when printing test log

Currently, kunit_parser.py is stripping all leading whitespace to make
parsing easier. But this means we can't accurately show kernel output
for failing tests or when the kernel crashes.

Embarassingly, this affects even KUnit's own output, e.g.
[13:40:46] Expected 2 + 1 == 2, but
[13:40:46] 2 + 1 == 3 (0x3)
[13:40:46] not ok 1 example_simple_test
[13:40:46] [FAILED] example_simple_test

After this change, here's what the output in context would look like
[13:40:46] =================== example (4 subtests) ===================
[13:40:46] # example_simple_test: initializing
[13:40:46] # example_simple_test: EXPECTATION FAILED at lib/kunit/kunit-example-test.c:29
[13:40:46] Expected 2 + 1 == 2, but
[13:40:46] 2 + 1 == 3 (0x3)
[13:40:46] [FAILED] example_simple_test
[13:40:46] [SKIPPED] example_skip_test
[13:40:46] [SKIPPED] example_mark_skipped_test
[13:40:46] [PASSED] example_all_expect_macros_test
[13:40:46] # example: initializing suite
[13:40:46] # example: pass:1 fail:1 skip:2 total:4
[13:40:46] # Totals: pass:1 fail:1 skip:2 total:4
[13:40:46] ===================== [FAILED] example =====================

This example shows one minor cosmetic defect this approach has.
The test counts lines prevent us from dedenting the suite-level output.
But at the same time, any form of non-KUnit output would do the same
unless it happened to be indented as well.

Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: David Gow <davidgow@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>

authored by

Daniel Latypov and committed by
Shuah Khan
c2bb92bc a81fe7ec

+16 -15
+1 -1
tools/testing/kunit/kunit.py
··· 202 202 if request.raw_output == 'all': 203 203 pass 204 204 elif request.raw_output == 'kunit': 205 - output = kunit_parser.extract_tap_lines(output, lstrip=False) 205 + output = kunit_parser.extract_tap_lines(output) 206 206 for line in output: 207 207 print(line.rstrip()) 208 208 parse_time = time.time() - parse_start
+13 -14
tools/testing/kunit/kunit_parser.py
··· 13 13 from dataclasses import dataclass 14 14 import re 15 15 import sys 16 + import textwrap 16 17 17 18 from enum import Enum, auto 18 19 from typing import Iterable, Iterator, List, Optional, Tuple ··· 209 208 210 209 # Parsing helper methods: 211 210 212 - KTAP_START = re.compile(r'KTAP version ([0-9]+)$') 213 - TAP_START = re.compile(r'TAP version ([0-9]+)$') 214 - KTAP_END = re.compile('(List of all partitions:|' 211 + KTAP_START = re.compile(r'\s*KTAP version ([0-9]+)$') 212 + TAP_START = re.compile(r'\s*TAP version ([0-9]+)$') 213 + KTAP_END = re.compile(r'\s*(List of all partitions:|' 215 214 'Kernel panic - not syncing: VFS:|reboot: System halted)') 216 215 217 - def extract_tap_lines(kernel_output: Iterable[str], lstrip=True) -> LineStream: 216 + def extract_tap_lines(kernel_output: Iterable[str]) -> LineStream: 218 217 """Extracts KTAP lines from the kernel output.""" 219 218 def isolate_ktap_output(kernel_output: Iterable[str]) \ 220 219 -> Iterator[Tuple[int, str]]: ··· 240 239 # stop extracting KTAP lines 241 240 break 242 241 elif started: 243 - # remove the prefix and optionally any leading 244 - # whitespace. Our parsing logic relies on this. 242 + # remove the prefix, if any. 245 243 line = line[prefix_len:] 246 - if lstrip: 247 - line = line.lstrip() 248 244 yield line_num, line 249 245 return LineStream(lines=isolate_ktap_output(kernel_output)) 250 246 ··· 296 298 lines.pop() 297 299 return True 298 300 299 - TEST_HEADER = re.compile(r'^# Subtest: (.*)$') 301 + TEST_HEADER = re.compile(r'^\s*# Subtest: (.*)$') 300 302 301 303 def parse_test_header(lines: LineStream, test: Test) -> bool: 302 304 """ ··· 320 322 lines.pop() 321 323 return True 322 324 323 - TEST_PLAN = re.compile(r'1\.\.([0-9]+)') 325 + TEST_PLAN = re.compile(r'^\s*1\.\.([0-9]+)') 324 326 325 327 def parse_test_plan(lines: LineStream, test: Test) -> bool: 326 328 """ ··· 348 350 lines.pop() 349 351 return True 350 352 351 - TEST_RESULT = re.compile(r'^(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$') 353 + TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$') 352 354 353 - TEST_RESULT_SKIP = re.compile(r'^(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$') 355 + TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$') 354 356 355 357 def peek_test_name_match(lines: LineStream, test: Test) -> bool: 356 358 """ ··· 509 511 510 512 def print_log(log: Iterable[str]) -> None: 511 513 """Prints all strings in saved log for test in yellow.""" 512 - for m in log: 513 - stdout.print_with_timestamp(stdout.yellow(m)) 514 + formatted = textwrap.dedent('\n'.join(log)) 515 + for line in formatted.splitlines(): 516 + stdout.print_with_timestamp(stdout.yellow(line)) 514 517 515 518 def format_test_result(test: Test) -> str: 516 519 """
+2
tools/testing/kunit/kunit_tool_test.py
··· 336 336 KTAP version 1 337 337 1..1 338 338 Test output. 339 + Indented more. 339 340 not ok 1 test1 340 341 """ 341 342 result = kunit_parser.parse_run_tests(output.splitlines()) 342 343 self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status) 343 344 344 345 self.print_mock.assert_any_call(StrContains('Test output.')) 346 + self.print_mock.assert_any_call(StrContains(' Indented more.')) 345 347 self.noPrintCallContains('not ok 1 test1') 346 348 347 349 def line_stream_from_strs(strs: Iterable[str]) -> kunit_parser.LineStream: