xref: /linux/tools/testing/kunit/kunit_parser.py (revision e56e482855b782b0e38b91cc383324bb80461073)
1# SPDX-License-Identifier: GPL-2.0
2#
3# Parses KTAP test results from a kernel dmesg log and incrementally prints
4# results with reader-friendly format. Stores and returns test results in a
5# Test object.
6#
7# Copyright (C) 2019, Google LLC.
8# Author: Felix Guo <felixguoxiuping@gmail.com>
9# Author: Brendan Higgins <brendanhiggins@google.com>
10# Author: Rae Moar <rmoar@google.com>
11
12from __future__ import annotations
13import re
14
15from collections import namedtuple
16from datetime import datetime
17from enum import Enum, auto
18from functools import reduce
19from typing import Iterable, Iterator, List, Optional, Tuple
20
21TestResult = namedtuple('TestResult', ['status','test','log'])
22
23class Test(object):
24	"""
25	A class to represent a test parsed from KTAP results. All KTAP
26	results within a test log are stored in a main Test object as
27	subtests.
28
29	Attributes:
30	status : TestStatus - status of the test
31	name : str - name of the test
32	expected_count : int - expected number of subtests (0 if single
33		test case and None if unknown expected number of subtests)
34	subtests : List[Test] - list of subtests
35	log : List[str] - log of KTAP lines that correspond to the test
36	counts : TestCounts - counts of the test statuses and errors of
37		subtests or of the test itself if the test is a single
38		test case.
39	"""
40	def __init__(self) -> None:
41		"""Creates Test object with default attributes."""
42		self.status = TestStatus.TEST_CRASHED
43		self.name = ''
44		self.expected_count = 0  # type: Optional[int]
45		self.subtests = []  # type: List[Test]
46		self.log = []  # type: List[str]
47		self.counts = TestCounts()
48
49	def __str__(self) -> str:
50		"""Returns string representation of a Test class object."""
51		return ('Test(' + str(self.status) + ', ' + self.name +
52			', ' + str(self.expected_count) + ', ' +
53			str(self.subtests) + ', ' + str(self.log) + ', ' +
54			str(self.counts) + ')')
55
56	def __repr__(self) -> str:
57		"""Returns string representation of a Test class object."""
58		return str(self)
59
60	def add_error(self, error_message: str) -> None:
61		"""Records an error that occurred while parsing this test."""
62		self.counts.errors += 1
63		print_error('Test ' + self.name + ': ' + error_message)
64
65class TestStatus(Enum):
66	"""An enumeration class to represent the status of a test."""
67	SUCCESS = auto()
68	FAILURE = auto()
69	SKIPPED = auto()
70	TEST_CRASHED = auto()
71	NO_TESTS = auto()
72	FAILURE_TO_PARSE_TESTS = auto()
73
74class TestCounts:
75	"""
76	Tracks the counts of statuses of all test cases and any errors within
77	a Test.
78
79	Attributes:
80	passed : int - the number of tests that have passed
81	failed : int - the number of tests that have failed
82	crashed : int - the number of tests that have crashed
83	skipped : int - the number of tests that have skipped
84	errors : int - the number of errors in the test and subtests
85	"""
86	def __init__(self):
87		"""Creates TestCounts object with counts of all test
88		statuses and test errors set to 0.
89		"""
90		self.passed = 0
91		self.failed = 0
92		self.crashed = 0
93		self.skipped = 0
94		self.errors = 0
95
96	def __str__(self) -> str:
97		"""Returns the string representation of a TestCounts object.
98		"""
99		return ('Passed: ' + str(self.passed) +
100			', Failed: ' + str(self.failed) +
101			', Crashed: ' + str(self.crashed) +
102			', Skipped: ' + str(self.skipped) +
103			', Errors: ' + str(self.errors))
104
105	def total(self) -> int:
106		"""Returns the total number of test cases within a test
107		object, where a test case is a test with no subtests.
108		"""
109		return (self.passed + self.failed + self.crashed +
110			self.skipped)
111
112	def add_subtest_counts(self, counts: TestCounts) -> None:
113		"""
114		Adds the counts of another TestCounts object to the current
115		TestCounts object. Used to add the counts of a subtest to the
116		parent test.
117
118		Parameters:
119		counts - a different TestCounts object whose counts
120			will be added to the counts of the TestCounts object
121		"""
122		self.passed += counts.passed
123		self.failed += counts.failed
124		self.crashed += counts.crashed
125		self.skipped += counts.skipped
126		self.errors += counts.errors
127
128	def get_status(self) -> TestStatus:
129		"""Returns the aggregated status of a Test using test
130		counts.
131		"""
132		if self.total() == 0:
133			return TestStatus.NO_TESTS
134		elif self.crashed:
135			# If one of the subtests crash, the expected status
136			# of the Test is crashed.
137			return TestStatus.TEST_CRASHED
138		elif self.failed:
139			# Otherwise if one of the subtests fail, the
140			# expected status of the Test is failed.
141			return TestStatus.FAILURE
142		elif self.passed:
143			# Otherwise if one of the subtests pass, the
144			# expected status of the Test is passed.
145			return TestStatus.SUCCESS
146		else:
147			# Finally, if none of the subtests have failed,
148			# crashed, or passed, the expected status of the
149			# Test is skipped.
150			return TestStatus.SKIPPED
151
152	def add_status(self, status: TestStatus) -> None:
153		"""
154		Increments count of inputted status.
155
156		Parameters:
157		status - status to be added to the TestCounts object
158		"""
159		if status == TestStatus.SUCCESS:
160			self.passed += 1
161		elif status == TestStatus.FAILURE:
162			self.failed += 1
163		elif status == TestStatus.SKIPPED:
164			self.skipped += 1
165		elif status != TestStatus.NO_TESTS:
166			self.crashed += 1
167
168class LineStream:
169	"""
170	A class to represent the lines of kernel output.
171	Provides a peek()/pop() interface over an iterator of
172	(line#, text).
173	"""
174	_lines: Iterator[Tuple[int, str]]
175	_next: Tuple[int, str]
176	_done: bool
177
178	def __init__(self, lines: Iterator[Tuple[int, str]]):
179		"""Creates a new LineStream that wraps the given iterator."""
180		self._lines = lines
181		self._done = False
182		self._next = (0, '')
183		self._get_next()
184
185	def _get_next(self) -> None:
186		"""Advances the LineSteam to the next line."""
187		try:
188			self._next = next(self._lines)
189		except StopIteration:
190			self._done = True
191
192	def peek(self) -> str:
193		"""Returns the current line, without advancing the LineStream.
194		"""
195		return self._next[1]
196
197	def pop(self) -> str:
198		"""Returns the current line and advances the LineStream to
199		the next line.
200		"""
201		n = self._next
202		self._get_next()
203		return n[1]
204
205	def __bool__(self) -> bool:
206		"""Returns True if stream has more lines."""
207		return not self._done
208
209	# Only used by kunit_tool_test.py.
210	def __iter__(self) -> Iterator[str]:
211		"""Empties all lines stored in LineStream object into
212		Iterator object and returns the Iterator object.
213		"""
214		while bool(self):
215			yield self.pop()
216
217	def line_number(self) -> int:
218		"""Returns the line number of the current line."""
219		return self._next[0]
220
221# Parsing helper methods:
222
223KTAP_START = re.compile(r'KTAP version ([0-9]+)$')
224TAP_START = re.compile(r'TAP version ([0-9]+)$')
225KTAP_END = re.compile('(List of all partitions:|'
226	'Kernel panic - not syncing: VFS:|reboot: System halted)')
227
228def extract_tap_lines(kernel_output: Iterable[str]) -> LineStream:
229	"""Extracts KTAP lines from the kernel output."""
230	def isolate_ktap_output(kernel_output: Iterable[str]) \
231			-> Iterator[Tuple[int, str]]:
232		line_num = 0
233		started = False
234		for line in kernel_output:
235			line_num += 1
236			line = line.rstrip()  # remove trailing \n
237			if not started and KTAP_START.search(line):
238				# start extracting KTAP lines and set prefix
239				# to number of characters before version line
240				prefix_len = len(
241					line.split('KTAP version')[0])
242				started = True
243				yield line_num, line[prefix_len:]
244			elif not started and TAP_START.search(line):
245				# start extracting KTAP lines and set prefix
246				# to number of characters before version line
247				prefix_len = len(line.split('TAP version')[0])
248				started = True
249				yield line_num, line[prefix_len:]
250			elif started and KTAP_END.search(line):
251				# stop extracting KTAP lines
252				break
253			elif started:
254				# remove prefix and any indention and yield
255				# line with line number
256				line = line[prefix_len:].lstrip()
257				yield line_num, line
258	return LineStream(lines=isolate_ktap_output(kernel_output))
259
260KTAP_VERSIONS = [1]
261TAP_VERSIONS = [13, 14]
262
263def check_version(version_num: int, accepted_versions: List[int],
264			version_type: str, test: Test) -> None:
265	"""
266	Adds error to test object if version number is too high or too
267	low.
268
269	Parameters:
270	version_num - The inputted version number from the parsed KTAP or TAP
271		header line
272	accepted_version - List of accepted KTAP or TAP versions
273	version_type - 'KTAP' or 'TAP' depending on the type of
274		version line.
275	test - Test object for current test being parsed
276	"""
277	if version_num < min(accepted_versions):
278		test.add_error(version_type +
279			' version lower than expected!')
280	elif version_num > max(accepted_versions):
281		test.add_error(
282			version_type + ' version higher than expected!')
283
284def parse_ktap_header(lines: LineStream, test: Test) -> bool:
285	"""
286	Parses KTAP/TAP header line and checks version number.
287	Returns False if fails to parse KTAP/TAP header line.
288
289	Accepted formats:
290	- 'KTAP version [version number]'
291	- 'TAP version [version number]'
292
293	Parameters:
294	lines - LineStream of KTAP output to parse
295	test - Test object for current test being parsed
296
297	Return:
298	True if successfully parsed KTAP/TAP header line
299	"""
300	ktap_match = KTAP_START.match(lines.peek())
301	tap_match = TAP_START.match(lines.peek())
302	if ktap_match:
303		version_num = int(ktap_match.group(1))
304		check_version(version_num, KTAP_VERSIONS, 'KTAP', test)
305	elif tap_match:
306		version_num = int(tap_match.group(1))
307		check_version(version_num, TAP_VERSIONS, 'TAP', test)
308	else:
309		return False
310	test.log.append(lines.pop())
311	return True
312
313TEST_HEADER = re.compile(r'^# Subtest: (.*)$')
314
315def parse_test_header(lines: LineStream, test: Test) -> bool:
316	"""
317	Parses test header and stores test name in test object.
318	Returns False if fails to parse test header line.
319
320	Accepted format:
321	- '# Subtest: [test name]'
322
323	Parameters:
324	lines - LineStream of KTAP output to parse
325	test - Test object for current test being parsed
326
327	Return:
328	True if successfully parsed test header line
329	"""
330	match = TEST_HEADER.match(lines.peek())
331	if not match:
332		return False
333	test.log.append(lines.pop())
334	test.name = match.group(1)
335	return True
336
337TEST_PLAN = re.compile(r'1\.\.([0-9]+)')
338
339def parse_test_plan(lines: LineStream, test: Test) -> bool:
340	"""
341	Parses test plan line and stores the expected number of subtests in
342	test object. Reports an error if expected count is 0.
343	Returns False and sets expected_count to None if there is no valid test
344	plan.
345
346	Accepted format:
347	- '1..[number of subtests]'
348
349	Parameters:
350	lines - LineStream of KTAP output to parse
351	test - Test object for current test being parsed
352
353	Return:
354	True if successfully parsed test plan line
355	"""
356	match = TEST_PLAN.match(lines.peek())
357	if not match:
358		test.expected_count = None
359		return False
360	test.log.append(lines.pop())
361	expected_count = int(match.group(1))
362	test.expected_count = expected_count
363	return True
364
365TEST_RESULT = re.compile(r'^(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$')
366
367TEST_RESULT_SKIP = re.compile(r'^(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$')
368
369def peek_test_name_match(lines: LineStream, test: Test) -> bool:
370	"""
371	Matches current line with the format of a test result line and checks
372	if the name matches the name of the current test.
373	Returns False if fails to match format or name.
374
375	Accepted format:
376	- '[ok|not ok] [test number] [-] [test name] [optional skip
377		directive]'
378
379	Parameters:
380	lines - LineStream of KTAP output to parse
381	test - Test object for current test being parsed
382
383	Return:
384	True if matched a test result line and the name matching the
385		expected test name
386	"""
387	line = lines.peek()
388	match = TEST_RESULT.match(line)
389	if not match:
390		return False
391	name = match.group(4)
392	return (name == test.name)
393
394def parse_test_result(lines: LineStream, test: Test,
395			expected_num: int) -> bool:
396	"""
397	Parses test result line and stores the status and name in the test
398	object. Reports an error if the test number does not match expected
399	test number.
400	Returns False if fails to parse test result line.
401
402	Note that the SKIP directive is the only direction that causes a
403	change in status.
404
405	Accepted format:
406	- '[ok|not ok] [test number] [-] [test name] [optional skip
407		directive]'
408
409	Parameters:
410	lines - LineStream of KTAP output to parse
411	test - Test object for current test being parsed
412	expected_num - expected test number for current test
413
414	Return:
415	True if successfully parsed a test result line.
416	"""
417	line = lines.peek()
418	match = TEST_RESULT.match(line)
419	skip_match = TEST_RESULT_SKIP.match(line)
420
421	# Check if line matches test result line format
422	if not match:
423		return False
424	test.log.append(lines.pop())
425
426	# Set name of test object
427	if skip_match:
428		test.name = skip_match.group(4)
429	else:
430		test.name = match.group(4)
431
432	# Check test num
433	num = int(match.group(2))
434	if num != expected_num:
435		test.add_error('Expected test number ' +
436			str(expected_num) + ' but found ' + str(num))
437
438	# Set status of test object
439	status = match.group(1)
440	if skip_match:
441		test.status = TestStatus.SKIPPED
442	elif status == 'ok':
443		test.status = TestStatus.SUCCESS
444	else:
445		test.status = TestStatus.FAILURE
446	return True
447
448def parse_diagnostic(lines: LineStream) -> List[str]:
449	"""
450	Parse lines that do not match the format of a test result line or
451	test header line and returns them in list.
452
453	Line formats that are not parsed:
454	- '# Subtest: [test name]'
455	- '[ok|not ok] [test number] [-] [test name] [optional skip
456		directive]'
457
458	Parameters:
459	lines - LineStream of KTAP output to parse
460
461	Return:
462	Log of diagnostic lines
463	"""
464	log = []  # type: List[str]
465	while lines and not TEST_RESULT.match(lines.peek()) and not \
466			TEST_HEADER.match(lines.peek()):
467		log.append(lines.pop())
468	return log
469
470DIAGNOSTIC_CRASH_MESSAGE = re.compile(r'^# .*?: kunit test case crashed!$')
471
472def parse_crash_in_log(test: Test) -> bool:
473	"""
474	Iterate through the lines of the log to parse for crash message.
475	If crash message found, set status to crashed and return True.
476	Otherwise return False.
477
478	Parameters:
479	test - Test object for current test being parsed
480
481	Return:
482	True if crash message found in log
483	"""
484	for line in test.log:
485		if DIAGNOSTIC_CRASH_MESSAGE.match(line):
486			test.status = TestStatus.TEST_CRASHED
487			return True
488	return False
489
490
491# Printing helper methods:
492
493DIVIDER = '=' * 60
494
495RESET = '\033[0;0m'
496
497def red(text: str) -> str:
498	"""Returns inputted string with red color code."""
499	return '\033[1;31m' + text + RESET
500
501def yellow(text: str) -> str:
502	"""Returns inputted string with yellow color code."""
503	return '\033[1;33m' + text + RESET
504
505def green(text: str) -> str:
506	"""Returns inputted string with green color code."""
507	return '\033[1;32m' + text + RESET
508
509ANSI_LEN = len(red(''))
510
511def print_with_timestamp(message: str) -> None:
512	"""Prints message with timestamp at beginning."""
513	print('[%s] %s' % (datetime.now().strftime('%H:%M:%S'), message))
514
515def format_test_divider(message: str, len_message: int) -> str:
516	"""
517	Returns string with message centered in fixed width divider.
518
519	Example:
520	'===================== message example ====================='
521
522	Parameters:
523	message - message to be centered in divider line
524	len_message - length of the message to be printed such that
525		any characters of the color codes are not counted
526
527	Return:
528	String containing message centered in fixed width divider
529	"""
530	default_count = 3  # default number of dashes
531	len_1 = default_count
532	len_2 = default_count
533	difference = len(DIVIDER) - len_message - 2  # 2 spaces added
534	if difference > 0:
535		# calculate number of dashes for each side of the divider
536		len_1 = int(difference / 2)
537		len_2 = difference - len_1
538	return ('=' * len_1) + ' ' + message + ' ' + ('=' * len_2)
539
540def print_test_header(test: Test) -> None:
541	"""
542	Prints test header with test name and optionally the expected number
543	of subtests.
544
545	Example:
546	'=================== example (2 subtests) ==================='
547
548	Parameters:
549	test - Test object representing current test being printed
550	"""
551	message = test.name
552	if test.expected_count:
553		if test.expected_count == 1:
554			message += (' (' + str(test.expected_count) +
555				' subtest)')
556		else:
557			message += (' (' + str(test.expected_count) +
558				' subtests)')
559	print_with_timestamp(format_test_divider(message, len(message)))
560
561def print_log(log: Iterable[str]) -> None:
562	"""
563	Prints all strings in saved log for test in yellow.
564
565	Parameters:
566	log - Iterable object with all strings saved in log for test
567	"""
568	for m in log:
569		print_with_timestamp(yellow(m))
570
571def format_test_result(test: Test) -> str:
572	"""
573	Returns string with formatted test result with colored status and test
574	name.
575
576	Example:
577	'[PASSED] example'
578
579	Parameters:
580	test - Test object representing current test being printed
581
582	Return:
583	String containing formatted test result
584	"""
585	if test.status == TestStatus.SUCCESS:
586		return (green('[PASSED] ') + test.name)
587	elif test.status == TestStatus.SKIPPED:
588		return (yellow('[SKIPPED] ') + test.name)
589	elif test.status == TestStatus.NO_TESTS:
590		return (yellow('[NO TESTS RUN] ') + test.name)
591	elif test.status == TestStatus.TEST_CRASHED:
592		print_log(test.log)
593		return (red('[CRASHED] ') + test.name)
594	else:
595		print_log(test.log)
596		return (red('[FAILED] ') + test.name)
597
598def print_test_result(test: Test) -> None:
599	"""
600	Prints result line with status of test.
601
602	Example:
603	'[PASSED] example'
604
605	Parameters:
606	test - Test object representing current test being printed
607	"""
608	print_with_timestamp(format_test_result(test))
609
610def print_test_footer(test: Test) -> None:
611	"""
612	Prints test footer with status of test.
613
614	Example:
615	'===================== [PASSED] example ====================='
616
617	Parameters:
618	test - Test object representing current test being printed
619	"""
620	message = format_test_result(test)
621	print_with_timestamp(format_test_divider(message,
622		len(message) - ANSI_LEN))
623
624def print_summary_line(test: Test) -> None:
625	"""
626	Prints summary line of test object. Color of line is dependent on
627	status of test. Color is green if test passes, yellow if test is
628	skipped, and red if the test fails or crashes. Summary line contains
629	counts of the statuses of the tests subtests or the test itself if it
630	has no subtests.
631
632	Example:
633	"Testing complete. Passed: 2, Failed: 0, Crashed: 0, Skipped: 0,
634	Errors: 0"
635
636	test - Test object representing current test being printed
637	"""
638	if test.status == TestStatus.SUCCESS:
639		color = green
640	elif test.status == TestStatus.SKIPPED or test.status == TestStatus.NO_TESTS:
641		color = yellow
642	else:
643		color = red
644	counts = test.counts
645	print_with_timestamp(color('Testing complete. ' + str(counts)))
646
647def print_error(error_message: str) -> None:
648	"""
649	Prints error message with error format.
650
651	Example:
652	"[ERROR] Test example: missing test plan!"
653
654	Parameters:
655	error_message - message describing error
656	"""
657	print_with_timestamp(red('[ERROR] ') + error_message)
658
659# Other methods:
660
661def bubble_up_test_results(test: Test) -> None:
662	"""
663	If the test has subtests, add the test counts of the subtests to the
664	test and check if any of the tests crashed and if so set the test
665	status to crashed. Otherwise if the test has no subtests add the
666	status of the test to the test counts.
667
668	Parameters:
669	test - Test object for current test being parsed
670	"""
671	parse_crash_in_log(test)
672	subtests = test.subtests
673	counts = test.counts
674	status = test.status
675	for t in subtests:
676		counts.add_subtest_counts(t.counts)
677	if counts.total() == 0:
678		counts.add_status(status)
679	elif test.counts.get_status() == TestStatus.TEST_CRASHED:
680		test.status = TestStatus.TEST_CRASHED
681
682def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
683	"""
684	Finds next test to parse in LineStream, creates new Test object,
685	parses any subtests of the test, populates Test object with all
686	information (status, name) about the test and the Test objects for
687	any subtests, and then returns the Test object. The method accepts
688	three formats of tests:
689
690	Accepted test formats:
691
692	- Main KTAP/TAP header
693
694	Example:
695
696	KTAP version 1
697	1..4
698	[subtests]
699
700	- Subtest header line
701
702	Example:
703
704	# Subtest: name
705	1..3
706	[subtests]
707	ok 1 name
708
709	- Test result line
710
711	Example:
712
713	ok 1 - test
714
715	Parameters:
716	lines - LineStream of KTAP output to parse
717	expected_num - expected test number for test to be parsed
718	log - list of strings containing any preceding diagnostic lines
719		corresponding to the current test
720
721	Return:
722	Test object populated with characteristics and any subtests
723	"""
724	test = Test()
725	test.log.extend(log)
726	parent_test = False
727	main = parse_ktap_header(lines, test)
728	if main:
729		# If KTAP/TAP header is found, attempt to parse
730		# test plan
731		test.name = "main"
732		parse_test_plan(lines, test)
733		parent_test = True
734	else:
735		# If KTAP/TAP header is not found, test must be subtest
736		# header or test result line so parse attempt to parser
737		# subtest header
738		parent_test = parse_test_header(lines, test)
739		if parent_test:
740			# If subtest header is found, attempt to parse
741			# test plan and print header
742			parse_test_plan(lines, test)
743			print_test_header(test)
744	expected_count = test.expected_count
745	subtests = []
746	test_num = 1
747	while parent_test and (expected_count is None or test_num <= expected_count):
748		# Loop to parse any subtests.
749		# Break after parsing expected number of tests or
750		# if expected number of tests is unknown break when test
751		# result line with matching name to subtest header is found
752		# or no more lines in stream.
753		sub_log = parse_diagnostic(lines)
754		sub_test = Test()
755		if not lines or (peek_test_name_match(lines, test) and
756				not main):
757			if expected_count and test_num <= expected_count:
758				# If parser reaches end of test before
759				# parsing expected number of subtests, print
760				# crashed subtest and record error
761				test.add_error('missing expected subtest!')
762				sub_test.log.extend(sub_log)
763				test.counts.add_status(
764					TestStatus.TEST_CRASHED)
765				print_test_result(sub_test)
766			else:
767				test.log.extend(sub_log)
768				break
769		else:
770			sub_test = parse_test(lines, test_num, sub_log)
771		subtests.append(sub_test)
772		test_num += 1
773	test.subtests = subtests
774	if not main:
775		# If not main test, look for test result line
776		test.log.extend(parse_diagnostic(lines))
777		if (parent_test and peek_test_name_match(lines, test)) or \
778				not parent_test:
779			parse_test_result(lines, test, expected_num)
780		else:
781			test.add_error('missing subtest result line!')
782
783	# Check for there being no tests
784	if parent_test and len(subtests) == 0:
785		test.status = TestStatus.NO_TESTS
786		test.add_error('0 tests run!')
787
788	# Add statuses to TestCounts attribute in Test object
789	bubble_up_test_results(test)
790	if parent_test and not main:
791		# If test has subtests and is not the main test object, print
792		# footer.
793		print_test_footer(test)
794	elif not main:
795		print_test_result(test)
796	return test
797
798def parse_run_tests(kernel_output: Iterable[str]) -> TestResult:
799	"""
800	Using kernel output, extract KTAP lines, parse the lines for test
801	results and print condensed test results and summary line .
802
803	Parameters:
804	kernel_output - Iterable object contains lines of kernel output
805
806	Return:
807	TestResult - Tuple containg status of main test object, main test
808		object with all subtests, and log of all KTAP lines.
809	"""
810	print_with_timestamp(DIVIDER)
811	lines = extract_tap_lines(kernel_output)
812	test = Test()
813	if not lines:
814		test.add_error('invalid KTAP input!')
815		test.status = TestStatus.FAILURE_TO_PARSE_TESTS
816	else:
817		test = parse_test(lines, 0, [])
818		if test.status != TestStatus.NO_TESTS:
819			test.status = test.counts.get_status()
820	print_with_timestamp(DIVIDER)
821	print_summary_line(test)
822	return TestResult(test.status, test, lines)
823