162306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci# 362306a36Sopenharmony_ci# Parses KTAP test results from a kernel dmesg log and incrementally prints 462306a36Sopenharmony_ci# results with reader-friendly format. Stores and returns test results in a 562306a36Sopenharmony_ci# Test object. 662306a36Sopenharmony_ci# 762306a36Sopenharmony_ci# Copyright (C) 2019, Google LLC. 862306a36Sopenharmony_ci# Author: Felix Guo <felixguoxiuping@gmail.com> 962306a36Sopenharmony_ci# Author: Brendan Higgins <brendanhiggins@google.com> 1062306a36Sopenharmony_ci# Author: Rae Moar <rmoar@google.com> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cifrom __future__ import annotations 1362306a36Sopenharmony_cifrom dataclasses import dataclass 1462306a36Sopenharmony_ciimport re 1562306a36Sopenharmony_ciimport textwrap 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cifrom enum import Enum, auto 1862306a36Sopenharmony_cifrom typing import Iterable, Iterator, List, Optional, Tuple 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_cifrom kunit_printer import stdout 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ciclass Test: 2362306a36Sopenharmony_ci """ 2462306a36Sopenharmony_ci A class to represent a test parsed from KTAP results. All KTAP 2562306a36Sopenharmony_ci results within a test log are stored in a main Test object as 2662306a36Sopenharmony_ci subtests. 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci Attributes: 2962306a36Sopenharmony_ci status : TestStatus - status of the test 3062306a36Sopenharmony_ci name : str - name of the test 3162306a36Sopenharmony_ci expected_count : int - expected number of subtests (0 if single 3262306a36Sopenharmony_ci test case and None if unknown expected number of subtests) 3362306a36Sopenharmony_ci subtests : List[Test] - list of subtests 3462306a36Sopenharmony_ci log : List[str] - log of KTAP lines that correspond to the test 3562306a36Sopenharmony_ci counts : TestCounts - counts of the test statuses and errors of 3662306a36Sopenharmony_ci subtests or of the test itself if the test is a single 3762306a36Sopenharmony_ci test case. 3862306a36Sopenharmony_ci """ 3962306a36Sopenharmony_ci def __init__(self) -> None: 4062306a36Sopenharmony_ci """Creates Test object with default attributes.""" 4162306a36Sopenharmony_ci self.status = TestStatus.TEST_CRASHED 4262306a36Sopenharmony_ci self.name = '' 4362306a36Sopenharmony_ci self.expected_count = 0 # type: Optional[int] 4462306a36Sopenharmony_ci self.subtests = [] # type: List[Test] 4562306a36Sopenharmony_ci self.log = [] # type: List[str] 4662306a36Sopenharmony_ci self.counts = TestCounts() 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci def __str__(self) -> str: 4962306a36Sopenharmony_ci """Returns string representation of a Test class object.""" 5062306a36Sopenharmony_ci return (f'Test({self.status}, {self.name}, {self.expected_count}, ' 5162306a36Sopenharmony_ci f'{self.subtests}, {self.log}, {self.counts})') 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci def __repr__(self) -> str: 5462306a36Sopenharmony_ci """Returns string representation of a Test class object.""" 5562306a36Sopenharmony_ci return str(self) 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci def add_error(self, error_message: str) -> None: 5862306a36Sopenharmony_ci """Records an error that occurred while parsing this test.""" 5962306a36Sopenharmony_ci self.counts.errors += 1 6062306a36Sopenharmony_ci stdout.print_with_timestamp(stdout.red('[ERROR]') + f' Test: {self.name}: {error_message}') 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci def ok_status(self) -> bool: 6362306a36Sopenharmony_ci """Returns true if the status was ok, i.e. passed or skipped.""" 6462306a36Sopenharmony_ci return self.status in (TestStatus.SUCCESS, TestStatus.SKIPPED) 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ciclass TestStatus(Enum): 6762306a36Sopenharmony_ci """An enumeration class to represent the status of a test.""" 6862306a36Sopenharmony_ci SUCCESS = auto() 6962306a36Sopenharmony_ci FAILURE = auto() 7062306a36Sopenharmony_ci SKIPPED = auto() 7162306a36Sopenharmony_ci TEST_CRASHED = auto() 7262306a36Sopenharmony_ci NO_TESTS = auto() 7362306a36Sopenharmony_ci FAILURE_TO_PARSE_TESTS = auto() 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci@dataclass 7662306a36Sopenharmony_ciclass TestCounts: 7762306a36Sopenharmony_ci """ 7862306a36Sopenharmony_ci Tracks the counts of statuses of all test cases and any errors within 7962306a36Sopenharmony_ci a Test. 8062306a36Sopenharmony_ci """ 8162306a36Sopenharmony_ci passed: int = 0 8262306a36Sopenharmony_ci failed: int = 0 8362306a36Sopenharmony_ci crashed: int = 0 8462306a36Sopenharmony_ci skipped: int = 0 8562306a36Sopenharmony_ci errors: int = 0 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci def __str__(self) -> str: 8862306a36Sopenharmony_ci """Returns the string representation of a TestCounts object.""" 8962306a36Sopenharmony_ci statuses = [('passed', self.passed), ('failed', self.failed), 9062306a36Sopenharmony_ci ('crashed', self.crashed), ('skipped', self.skipped), 9162306a36Sopenharmony_ci ('errors', self.errors)] 9262306a36Sopenharmony_ci return f'Ran {self.total()} tests: ' + \ 9362306a36Sopenharmony_ci ', '.join(f'{s}: {n}' for s, n in statuses if n > 0) 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci def total(self) -> int: 9662306a36Sopenharmony_ci """Returns the total number of test cases within a test 9762306a36Sopenharmony_ci object, where a test case is a test with no subtests. 9862306a36Sopenharmony_ci """ 9962306a36Sopenharmony_ci return (self.passed + self.failed + self.crashed + 10062306a36Sopenharmony_ci self.skipped) 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci def add_subtest_counts(self, counts: TestCounts) -> None: 10362306a36Sopenharmony_ci """ 10462306a36Sopenharmony_ci Adds the counts of another TestCounts object to the current 10562306a36Sopenharmony_ci TestCounts object. Used to add the counts of a subtest to the 10662306a36Sopenharmony_ci parent test. 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci Parameters: 10962306a36Sopenharmony_ci counts - a different TestCounts object whose counts 11062306a36Sopenharmony_ci will be added to the counts of the TestCounts object 11162306a36Sopenharmony_ci """ 11262306a36Sopenharmony_ci self.passed += counts.passed 11362306a36Sopenharmony_ci self.failed += counts.failed 11462306a36Sopenharmony_ci self.crashed += counts.crashed 11562306a36Sopenharmony_ci self.skipped += counts.skipped 11662306a36Sopenharmony_ci self.errors += counts.errors 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci def get_status(self) -> TestStatus: 11962306a36Sopenharmony_ci """Returns the aggregated status of a Test using test 12062306a36Sopenharmony_ci counts. 12162306a36Sopenharmony_ci """ 12262306a36Sopenharmony_ci if self.total() == 0: 12362306a36Sopenharmony_ci return TestStatus.NO_TESTS 12462306a36Sopenharmony_ci if self.crashed: 12562306a36Sopenharmony_ci # Crashes should take priority. 12662306a36Sopenharmony_ci return TestStatus.TEST_CRASHED 12762306a36Sopenharmony_ci if self.failed: 12862306a36Sopenharmony_ci return TestStatus.FAILURE 12962306a36Sopenharmony_ci if self.passed: 13062306a36Sopenharmony_ci # No failures or crashes, looks good! 13162306a36Sopenharmony_ci return TestStatus.SUCCESS 13262306a36Sopenharmony_ci # We have only skipped tests. 13362306a36Sopenharmony_ci return TestStatus.SKIPPED 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci def add_status(self, status: TestStatus) -> None: 13662306a36Sopenharmony_ci """Increments the count for `status`.""" 13762306a36Sopenharmony_ci if status == TestStatus.SUCCESS: 13862306a36Sopenharmony_ci self.passed += 1 13962306a36Sopenharmony_ci elif status == TestStatus.FAILURE: 14062306a36Sopenharmony_ci self.failed += 1 14162306a36Sopenharmony_ci elif status == TestStatus.SKIPPED: 14262306a36Sopenharmony_ci self.skipped += 1 14362306a36Sopenharmony_ci elif status != TestStatus.NO_TESTS: 14462306a36Sopenharmony_ci self.crashed += 1 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ciclass LineStream: 14762306a36Sopenharmony_ci """ 14862306a36Sopenharmony_ci A class to represent the lines of kernel output. 14962306a36Sopenharmony_ci Provides a lazy peek()/pop() interface over an iterator of 15062306a36Sopenharmony_ci (line#, text). 15162306a36Sopenharmony_ci """ 15262306a36Sopenharmony_ci _lines: Iterator[Tuple[int, str]] 15362306a36Sopenharmony_ci _next: Tuple[int, str] 15462306a36Sopenharmony_ci _need_next: bool 15562306a36Sopenharmony_ci _done: bool 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci def __init__(self, lines: Iterator[Tuple[int, str]]): 15862306a36Sopenharmony_ci """Creates a new LineStream that wraps the given iterator.""" 15962306a36Sopenharmony_ci self._lines = lines 16062306a36Sopenharmony_ci self._done = False 16162306a36Sopenharmony_ci self._need_next = True 16262306a36Sopenharmony_ci self._next = (0, '') 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci def _get_next(self) -> None: 16562306a36Sopenharmony_ci """Advances the LineSteam to the next line, if necessary.""" 16662306a36Sopenharmony_ci if not self._need_next: 16762306a36Sopenharmony_ci return 16862306a36Sopenharmony_ci try: 16962306a36Sopenharmony_ci self._next = next(self._lines) 17062306a36Sopenharmony_ci except StopIteration: 17162306a36Sopenharmony_ci self._done = True 17262306a36Sopenharmony_ci finally: 17362306a36Sopenharmony_ci self._need_next = False 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci def peek(self) -> str: 17662306a36Sopenharmony_ci """Returns the current line, without advancing the LineStream. 17762306a36Sopenharmony_ci """ 17862306a36Sopenharmony_ci self._get_next() 17962306a36Sopenharmony_ci return self._next[1] 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci def pop(self) -> str: 18262306a36Sopenharmony_ci """Returns the current line and advances the LineStream to 18362306a36Sopenharmony_ci the next line. 18462306a36Sopenharmony_ci """ 18562306a36Sopenharmony_ci s = self.peek() 18662306a36Sopenharmony_ci if self._done: 18762306a36Sopenharmony_ci raise ValueError(f'LineStream: going past EOF, last line was {s}') 18862306a36Sopenharmony_ci self._need_next = True 18962306a36Sopenharmony_ci return s 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci def __bool__(self) -> bool: 19262306a36Sopenharmony_ci """Returns True if stream has more lines.""" 19362306a36Sopenharmony_ci self._get_next() 19462306a36Sopenharmony_ci return not self._done 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci # Only used by kunit_tool_test.py. 19762306a36Sopenharmony_ci def __iter__(self) -> Iterator[str]: 19862306a36Sopenharmony_ci """Empties all lines stored in LineStream object into 19962306a36Sopenharmony_ci Iterator object and returns the Iterator object. 20062306a36Sopenharmony_ci """ 20162306a36Sopenharmony_ci while bool(self): 20262306a36Sopenharmony_ci yield self.pop() 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci def line_number(self) -> int: 20562306a36Sopenharmony_ci """Returns the line number of the current line.""" 20662306a36Sopenharmony_ci self._get_next() 20762306a36Sopenharmony_ci return self._next[0] 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci# Parsing helper methods: 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ciKTAP_START = re.compile(r'\s*KTAP version ([0-9]+)$') 21262306a36Sopenharmony_ciTAP_START = re.compile(r'\s*TAP version ([0-9]+)$') 21362306a36Sopenharmony_ciKTAP_END = re.compile(r'\s*(List of all partitions:|' 21462306a36Sopenharmony_ci 'Kernel panic - not syncing: VFS:|reboot: System halted)') 21562306a36Sopenharmony_ciEXECUTOR_ERROR = re.compile(r'\s*kunit executor: (.*)$') 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cidef extract_tap_lines(kernel_output: Iterable[str]) -> LineStream: 21862306a36Sopenharmony_ci """Extracts KTAP lines from the kernel output.""" 21962306a36Sopenharmony_ci def isolate_ktap_output(kernel_output: Iterable[str]) \ 22062306a36Sopenharmony_ci -> Iterator[Tuple[int, str]]: 22162306a36Sopenharmony_ci line_num = 0 22262306a36Sopenharmony_ci started = False 22362306a36Sopenharmony_ci for line in kernel_output: 22462306a36Sopenharmony_ci line_num += 1 22562306a36Sopenharmony_ci line = line.rstrip() # remove trailing \n 22662306a36Sopenharmony_ci if not started and KTAP_START.search(line): 22762306a36Sopenharmony_ci # start extracting KTAP lines and set prefix 22862306a36Sopenharmony_ci # to number of characters before version line 22962306a36Sopenharmony_ci prefix_len = len( 23062306a36Sopenharmony_ci line.split('KTAP version')[0]) 23162306a36Sopenharmony_ci started = True 23262306a36Sopenharmony_ci yield line_num, line[prefix_len:] 23362306a36Sopenharmony_ci elif not started and TAP_START.search(line): 23462306a36Sopenharmony_ci # start extracting KTAP lines and set prefix 23562306a36Sopenharmony_ci # to number of characters before version line 23662306a36Sopenharmony_ci prefix_len = len(line.split('TAP version')[0]) 23762306a36Sopenharmony_ci started = True 23862306a36Sopenharmony_ci yield line_num, line[prefix_len:] 23962306a36Sopenharmony_ci elif started and KTAP_END.search(line): 24062306a36Sopenharmony_ci # stop extracting KTAP lines 24162306a36Sopenharmony_ci break 24262306a36Sopenharmony_ci elif started: 24362306a36Sopenharmony_ci # remove the prefix, if any. 24462306a36Sopenharmony_ci line = line[prefix_len:] 24562306a36Sopenharmony_ci yield line_num, line 24662306a36Sopenharmony_ci elif EXECUTOR_ERROR.search(line): 24762306a36Sopenharmony_ci yield line_num, line 24862306a36Sopenharmony_ci return LineStream(lines=isolate_ktap_output(kernel_output)) 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ciKTAP_VERSIONS = [1] 25162306a36Sopenharmony_ciTAP_VERSIONS = [13, 14] 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cidef check_version(version_num: int, accepted_versions: List[int], 25462306a36Sopenharmony_ci version_type: str, test: Test) -> None: 25562306a36Sopenharmony_ci """ 25662306a36Sopenharmony_ci Adds error to test object if version number is too high or too 25762306a36Sopenharmony_ci low. 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci Parameters: 26062306a36Sopenharmony_ci version_num - The inputted version number from the parsed KTAP or TAP 26162306a36Sopenharmony_ci header line 26262306a36Sopenharmony_ci accepted_version - List of accepted KTAP or TAP versions 26362306a36Sopenharmony_ci version_type - 'KTAP' or 'TAP' depending on the type of 26462306a36Sopenharmony_ci version line. 26562306a36Sopenharmony_ci test - Test object for current test being parsed 26662306a36Sopenharmony_ci """ 26762306a36Sopenharmony_ci if version_num < min(accepted_versions): 26862306a36Sopenharmony_ci test.add_error(f'{version_type} version lower than expected!') 26962306a36Sopenharmony_ci elif version_num > max(accepted_versions): 27062306a36Sopenharmony_ci test.add_error(f'{version_type} version higer than expected!') 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cidef parse_ktap_header(lines: LineStream, test: Test) -> bool: 27362306a36Sopenharmony_ci """ 27462306a36Sopenharmony_ci Parses KTAP/TAP header line and checks version number. 27562306a36Sopenharmony_ci Returns False if fails to parse KTAP/TAP header line. 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci Accepted formats: 27862306a36Sopenharmony_ci - 'KTAP version [version number]' 27962306a36Sopenharmony_ci - 'TAP version [version number]' 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci Parameters: 28262306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 28362306a36Sopenharmony_ci test - Test object for current test being parsed 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci Return: 28662306a36Sopenharmony_ci True if successfully parsed KTAP/TAP header line 28762306a36Sopenharmony_ci """ 28862306a36Sopenharmony_ci ktap_match = KTAP_START.match(lines.peek()) 28962306a36Sopenharmony_ci tap_match = TAP_START.match(lines.peek()) 29062306a36Sopenharmony_ci if ktap_match: 29162306a36Sopenharmony_ci version_num = int(ktap_match.group(1)) 29262306a36Sopenharmony_ci check_version(version_num, KTAP_VERSIONS, 'KTAP', test) 29362306a36Sopenharmony_ci elif tap_match: 29462306a36Sopenharmony_ci version_num = int(tap_match.group(1)) 29562306a36Sopenharmony_ci check_version(version_num, TAP_VERSIONS, 'TAP', test) 29662306a36Sopenharmony_ci else: 29762306a36Sopenharmony_ci return False 29862306a36Sopenharmony_ci lines.pop() 29962306a36Sopenharmony_ci return True 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ciTEST_HEADER = re.compile(r'^\s*# Subtest: (.*)$') 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cidef parse_test_header(lines: LineStream, test: Test) -> bool: 30462306a36Sopenharmony_ci """ 30562306a36Sopenharmony_ci Parses test header and stores test name in test object. 30662306a36Sopenharmony_ci Returns False if fails to parse test header line. 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci Accepted format: 30962306a36Sopenharmony_ci - '# Subtest: [test name]' 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci Parameters: 31262306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 31362306a36Sopenharmony_ci test - Test object for current test being parsed 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci Return: 31662306a36Sopenharmony_ci True if successfully parsed test header line 31762306a36Sopenharmony_ci """ 31862306a36Sopenharmony_ci match = TEST_HEADER.match(lines.peek()) 31962306a36Sopenharmony_ci if not match: 32062306a36Sopenharmony_ci return False 32162306a36Sopenharmony_ci test.name = match.group(1) 32262306a36Sopenharmony_ci lines.pop() 32362306a36Sopenharmony_ci return True 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ciTEST_PLAN = re.compile(r'^\s*1\.\.([0-9]+)') 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cidef parse_test_plan(lines: LineStream, test: Test) -> bool: 32862306a36Sopenharmony_ci """ 32962306a36Sopenharmony_ci Parses test plan line and stores the expected number of subtests in 33062306a36Sopenharmony_ci test object. Reports an error if expected count is 0. 33162306a36Sopenharmony_ci Returns False and sets expected_count to None if there is no valid test 33262306a36Sopenharmony_ci plan. 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci Accepted format: 33562306a36Sopenharmony_ci - '1..[number of subtests]' 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci Parameters: 33862306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 33962306a36Sopenharmony_ci test - Test object for current test being parsed 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci Return: 34262306a36Sopenharmony_ci True if successfully parsed test plan line 34362306a36Sopenharmony_ci """ 34462306a36Sopenharmony_ci match = TEST_PLAN.match(lines.peek()) 34562306a36Sopenharmony_ci if not match: 34662306a36Sopenharmony_ci test.expected_count = None 34762306a36Sopenharmony_ci return False 34862306a36Sopenharmony_ci expected_count = int(match.group(1)) 34962306a36Sopenharmony_ci test.expected_count = expected_count 35062306a36Sopenharmony_ci lines.pop() 35162306a36Sopenharmony_ci return True 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ciTEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$') 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ciTEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$') 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_cidef peek_test_name_match(lines: LineStream, test: Test) -> bool: 35862306a36Sopenharmony_ci """ 35962306a36Sopenharmony_ci Matches current line with the format of a test result line and checks 36062306a36Sopenharmony_ci if the name matches the name of the current test. 36162306a36Sopenharmony_ci Returns False if fails to match format or name. 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci Accepted format: 36462306a36Sopenharmony_ci - '[ok|not ok] [test number] [-] [test name] [optional skip 36562306a36Sopenharmony_ci directive]' 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci Parameters: 36862306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 36962306a36Sopenharmony_ci test - Test object for current test being parsed 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci Return: 37262306a36Sopenharmony_ci True if matched a test result line and the name matching the 37362306a36Sopenharmony_ci expected test name 37462306a36Sopenharmony_ci """ 37562306a36Sopenharmony_ci line = lines.peek() 37662306a36Sopenharmony_ci match = TEST_RESULT.match(line) 37762306a36Sopenharmony_ci if not match: 37862306a36Sopenharmony_ci return False 37962306a36Sopenharmony_ci name = match.group(4) 38062306a36Sopenharmony_ci return name == test.name 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cidef parse_test_result(lines: LineStream, test: Test, 38362306a36Sopenharmony_ci expected_num: int) -> bool: 38462306a36Sopenharmony_ci """ 38562306a36Sopenharmony_ci Parses test result line and stores the status and name in the test 38662306a36Sopenharmony_ci object. Reports an error if the test number does not match expected 38762306a36Sopenharmony_ci test number. 38862306a36Sopenharmony_ci Returns False if fails to parse test result line. 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci Note that the SKIP directive is the only direction that causes a 39162306a36Sopenharmony_ci change in status. 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci Accepted format: 39462306a36Sopenharmony_ci - '[ok|not ok] [test number] [-] [test name] [optional skip 39562306a36Sopenharmony_ci directive]' 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci Parameters: 39862306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 39962306a36Sopenharmony_ci test - Test object for current test being parsed 40062306a36Sopenharmony_ci expected_num - expected test number for current test 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci Return: 40362306a36Sopenharmony_ci True if successfully parsed a test result line. 40462306a36Sopenharmony_ci """ 40562306a36Sopenharmony_ci line = lines.peek() 40662306a36Sopenharmony_ci match = TEST_RESULT.match(line) 40762306a36Sopenharmony_ci skip_match = TEST_RESULT_SKIP.match(line) 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci # Check if line matches test result line format 41062306a36Sopenharmony_ci if not match: 41162306a36Sopenharmony_ci return False 41262306a36Sopenharmony_ci lines.pop() 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci # Set name of test object 41562306a36Sopenharmony_ci if skip_match: 41662306a36Sopenharmony_ci test.name = skip_match.group(4) 41762306a36Sopenharmony_ci else: 41862306a36Sopenharmony_ci test.name = match.group(4) 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci # Check test num 42162306a36Sopenharmony_ci num = int(match.group(2)) 42262306a36Sopenharmony_ci if num != expected_num: 42362306a36Sopenharmony_ci test.add_error(f'Expected test number {expected_num} but found {num}') 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci # Set status of test object 42662306a36Sopenharmony_ci status = match.group(1) 42762306a36Sopenharmony_ci if skip_match: 42862306a36Sopenharmony_ci test.status = TestStatus.SKIPPED 42962306a36Sopenharmony_ci elif status == 'ok': 43062306a36Sopenharmony_ci test.status = TestStatus.SUCCESS 43162306a36Sopenharmony_ci else: 43262306a36Sopenharmony_ci test.status = TestStatus.FAILURE 43362306a36Sopenharmony_ci return True 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cidef parse_diagnostic(lines: LineStream) -> List[str]: 43662306a36Sopenharmony_ci """ 43762306a36Sopenharmony_ci Parse lines that do not match the format of a test result line or 43862306a36Sopenharmony_ci test header line and returns them in list. 43962306a36Sopenharmony_ci 44062306a36Sopenharmony_ci Line formats that are not parsed: 44162306a36Sopenharmony_ci - '# Subtest: [test name]' 44262306a36Sopenharmony_ci - '[ok|not ok] [test number] [-] [test name] [optional skip 44362306a36Sopenharmony_ci directive]' 44462306a36Sopenharmony_ci - 'KTAP version [version number]' 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci Parameters: 44762306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci Return: 45062306a36Sopenharmony_ci Log of diagnostic lines 45162306a36Sopenharmony_ci """ 45262306a36Sopenharmony_ci log = [] # type: List[str] 45362306a36Sopenharmony_ci non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START, TAP_START, TEST_PLAN] 45462306a36Sopenharmony_ci while lines and not any(re.match(lines.peek()) 45562306a36Sopenharmony_ci for re in non_diagnostic_lines): 45662306a36Sopenharmony_ci log.append(lines.pop()) 45762306a36Sopenharmony_ci return log 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci# Printing helper methods: 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ciDIVIDER = '=' * 60 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_cidef format_test_divider(message: str, len_message: int) -> str: 46562306a36Sopenharmony_ci """ 46662306a36Sopenharmony_ci Returns string with message centered in fixed width divider. 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci Example: 46962306a36Sopenharmony_ci '===================== message example =====================' 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci Parameters: 47262306a36Sopenharmony_ci message - message to be centered in divider line 47362306a36Sopenharmony_ci len_message - length of the message to be printed such that 47462306a36Sopenharmony_ci any characters of the color codes are not counted 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci Return: 47762306a36Sopenharmony_ci String containing message centered in fixed width divider 47862306a36Sopenharmony_ci """ 47962306a36Sopenharmony_ci default_count = 3 # default number of dashes 48062306a36Sopenharmony_ci len_1 = default_count 48162306a36Sopenharmony_ci len_2 = default_count 48262306a36Sopenharmony_ci difference = len(DIVIDER) - len_message - 2 # 2 spaces added 48362306a36Sopenharmony_ci if difference > 0: 48462306a36Sopenharmony_ci # calculate number of dashes for each side of the divider 48562306a36Sopenharmony_ci len_1 = int(difference / 2) 48662306a36Sopenharmony_ci len_2 = difference - len_1 48762306a36Sopenharmony_ci return ('=' * len_1) + f' {message} ' + ('=' * len_2) 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cidef print_test_header(test: Test) -> None: 49062306a36Sopenharmony_ci """ 49162306a36Sopenharmony_ci Prints test header with test name and optionally the expected number 49262306a36Sopenharmony_ci of subtests. 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci Example: 49562306a36Sopenharmony_ci '=================== example (2 subtests) ===================' 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci Parameters: 49862306a36Sopenharmony_ci test - Test object representing current test being printed 49962306a36Sopenharmony_ci """ 50062306a36Sopenharmony_ci message = test.name 50162306a36Sopenharmony_ci if message != "": 50262306a36Sopenharmony_ci # Add a leading space before the subtest counts only if a test name 50362306a36Sopenharmony_ci # is provided using a "# Subtest" header line. 50462306a36Sopenharmony_ci message += " " 50562306a36Sopenharmony_ci if test.expected_count: 50662306a36Sopenharmony_ci if test.expected_count == 1: 50762306a36Sopenharmony_ci message += '(1 subtest)' 50862306a36Sopenharmony_ci else: 50962306a36Sopenharmony_ci message += f'({test.expected_count} subtests)' 51062306a36Sopenharmony_ci stdout.print_with_timestamp(format_test_divider(message, len(message))) 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_cidef print_log(log: Iterable[str]) -> None: 51362306a36Sopenharmony_ci """Prints all strings in saved log for test in yellow.""" 51462306a36Sopenharmony_ci formatted = textwrap.dedent('\n'.join(log)) 51562306a36Sopenharmony_ci for line in formatted.splitlines(): 51662306a36Sopenharmony_ci stdout.print_with_timestamp(stdout.yellow(line)) 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_cidef format_test_result(test: Test) -> str: 51962306a36Sopenharmony_ci """ 52062306a36Sopenharmony_ci Returns string with formatted test result with colored status and test 52162306a36Sopenharmony_ci name. 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci Example: 52462306a36Sopenharmony_ci '[PASSED] example' 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci Parameters: 52762306a36Sopenharmony_ci test - Test object representing current test being printed 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci Return: 53062306a36Sopenharmony_ci String containing formatted test result 53162306a36Sopenharmony_ci """ 53262306a36Sopenharmony_ci if test.status == TestStatus.SUCCESS: 53362306a36Sopenharmony_ci return stdout.green('[PASSED] ') + test.name 53462306a36Sopenharmony_ci if test.status == TestStatus.SKIPPED: 53562306a36Sopenharmony_ci return stdout.yellow('[SKIPPED] ') + test.name 53662306a36Sopenharmony_ci if test.status == TestStatus.NO_TESTS: 53762306a36Sopenharmony_ci return stdout.yellow('[NO TESTS RUN] ') + test.name 53862306a36Sopenharmony_ci if test.status == TestStatus.TEST_CRASHED: 53962306a36Sopenharmony_ci print_log(test.log) 54062306a36Sopenharmony_ci return stdout.red('[CRASHED] ') + test.name 54162306a36Sopenharmony_ci print_log(test.log) 54262306a36Sopenharmony_ci return stdout.red('[FAILED] ') + test.name 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_cidef print_test_result(test: Test) -> None: 54562306a36Sopenharmony_ci """ 54662306a36Sopenharmony_ci Prints result line with status of test. 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci Example: 54962306a36Sopenharmony_ci '[PASSED] example' 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci Parameters: 55262306a36Sopenharmony_ci test - Test object representing current test being printed 55362306a36Sopenharmony_ci """ 55462306a36Sopenharmony_ci stdout.print_with_timestamp(format_test_result(test)) 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_cidef print_test_footer(test: Test) -> None: 55762306a36Sopenharmony_ci """ 55862306a36Sopenharmony_ci Prints test footer with status of test. 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_ci Example: 56162306a36Sopenharmony_ci '===================== [PASSED] example =====================' 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci Parameters: 56462306a36Sopenharmony_ci test - Test object representing current test being printed 56562306a36Sopenharmony_ci """ 56662306a36Sopenharmony_ci message = format_test_result(test) 56762306a36Sopenharmony_ci stdout.print_with_timestamp(format_test_divider(message, 56862306a36Sopenharmony_ci len(message) - stdout.color_len())) 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_cidef _summarize_failed_tests(test: Test) -> str: 57362306a36Sopenharmony_ci """Tries to summarize all the failing subtests in `test`.""" 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci def failed_names(test: Test, parent_name: str) -> List[str]: 57662306a36Sopenharmony_ci # Note: we use 'main' internally for the top-level test. 57762306a36Sopenharmony_ci if not parent_name or parent_name == 'main': 57862306a36Sopenharmony_ci full_name = test.name 57962306a36Sopenharmony_ci else: 58062306a36Sopenharmony_ci full_name = parent_name + '.' + test.name 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci if not test.subtests: # this is a leaf node 58362306a36Sopenharmony_ci return [full_name] 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci # If all the children failed, just say this subtest failed. 58662306a36Sopenharmony_ci # Don't summarize it down "the top-level test failed", though. 58762306a36Sopenharmony_ci failed_subtests = [sub for sub in test.subtests if not sub.ok_status()] 58862306a36Sopenharmony_ci if parent_name and len(failed_subtests) == len(test.subtests): 58962306a36Sopenharmony_ci return [full_name] 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci all_failures = [] # type: List[str] 59262306a36Sopenharmony_ci for t in failed_subtests: 59362306a36Sopenharmony_ci all_failures.extend(failed_names(t, full_name)) 59462306a36Sopenharmony_ci return all_failures 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci failures = failed_names(test, '') 59762306a36Sopenharmony_ci # If there are too many failures, printing them out will just be noisy. 59862306a36Sopenharmony_ci if len(failures) > 10: # this is an arbitrary limit 59962306a36Sopenharmony_ci return '' 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci return 'Failures: ' + ', '.join(failures) 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_cidef print_summary_line(test: Test) -> None: 60562306a36Sopenharmony_ci """ 60662306a36Sopenharmony_ci Prints summary line of test object. Color of line is dependent on 60762306a36Sopenharmony_ci status of test. Color is green if test passes, yellow if test is 60862306a36Sopenharmony_ci skipped, and red if the test fails or crashes. Summary line contains 60962306a36Sopenharmony_ci counts of the statuses of the tests subtests or the test itself if it 61062306a36Sopenharmony_ci has no subtests. 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci Example: 61362306a36Sopenharmony_ci "Testing complete. Passed: 2, Failed: 0, Crashed: 0, Skipped: 0, 61462306a36Sopenharmony_ci Errors: 0" 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci test - Test object representing current test being printed 61762306a36Sopenharmony_ci """ 61862306a36Sopenharmony_ci if test.status == TestStatus.SUCCESS: 61962306a36Sopenharmony_ci color = stdout.green 62062306a36Sopenharmony_ci elif test.status in (TestStatus.SKIPPED, TestStatus.NO_TESTS): 62162306a36Sopenharmony_ci color = stdout.yellow 62262306a36Sopenharmony_ci else: 62362306a36Sopenharmony_ci color = stdout.red 62462306a36Sopenharmony_ci stdout.print_with_timestamp(color(f'Testing complete. {test.counts}')) 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci # Summarize failures that might have gone off-screen since we had a lot 62762306a36Sopenharmony_ci # of tests (arbitrarily defined as >=100 for now). 62862306a36Sopenharmony_ci if test.ok_status() or test.counts.total() < 100: 62962306a36Sopenharmony_ci return 63062306a36Sopenharmony_ci summarized = _summarize_failed_tests(test) 63162306a36Sopenharmony_ci if not summarized: 63262306a36Sopenharmony_ci return 63362306a36Sopenharmony_ci stdout.print_with_timestamp(color(summarized)) 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci# Other methods: 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_cidef bubble_up_test_results(test: Test) -> None: 63862306a36Sopenharmony_ci """ 63962306a36Sopenharmony_ci If the test has subtests, add the test counts of the subtests to the 64062306a36Sopenharmony_ci test and check if any of the tests crashed and if so set the test 64162306a36Sopenharmony_ci status to crashed. Otherwise if the test has no subtests add the 64262306a36Sopenharmony_ci status of the test to the test counts. 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci Parameters: 64562306a36Sopenharmony_ci test - Test object for current test being parsed 64662306a36Sopenharmony_ci """ 64762306a36Sopenharmony_ci subtests = test.subtests 64862306a36Sopenharmony_ci counts = test.counts 64962306a36Sopenharmony_ci status = test.status 65062306a36Sopenharmony_ci for t in subtests: 65162306a36Sopenharmony_ci counts.add_subtest_counts(t.counts) 65262306a36Sopenharmony_ci if counts.total() == 0: 65362306a36Sopenharmony_ci counts.add_status(status) 65462306a36Sopenharmony_ci elif test.counts.get_status() == TestStatus.TEST_CRASHED: 65562306a36Sopenharmony_ci test.status = TestStatus.TEST_CRASHED 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cidef parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest: bool) -> Test: 65862306a36Sopenharmony_ci """ 65962306a36Sopenharmony_ci Finds next test to parse in LineStream, creates new Test object, 66062306a36Sopenharmony_ci parses any subtests of the test, populates Test object with all 66162306a36Sopenharmony_ci information (status, name) about the test and the Test objects for 66262306a36Sopenharmony_ci any subtests, and then returns the Test object. The method accepts 66362306a36Sopenharmony_ci three formats of tests: 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci Accepted test formats: 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci - Main KTAP/TAP header 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci Example: 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci KTAP version 1 67262306a36Sopenharmony_ci 1..4 67362306a36Sopenharmony_ci [subtests] 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci - Subtest header (must include either the KTAP version line or 67662306a36Sopenharmony_ci "# Subtest" header line) 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci Example (preferred format with both KTAP version line and 67962306a36Sopenharmony_ci "# Subtest" line): 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci KTAP version 1 68262306a36Sopenharmony_ci # Subtest: name 68362306a36Sopenharmony_ci 1..3 68462306a36Sopenharmony_ci [subtests] 68562306a36Sopenharmony_ci ok 1 name 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci Example (only "# Subtest" line): 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci # Subtest: name 69062306a36Sopenharmony_ci 1..3 69162306a36Sopenharmony_ci [subtests] 69262306a36Sopenharmony_ci ok 1 name 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci Example (only KTAP version line, compliant with KTAP v1 spec): 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci KTAP version 1 69762306a36Sopenharmony_ci 1..3 69862306a36Sopenharmony_ci [subtests] 69962306a36Sopenharmony_ci ok 1 name 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci - Test result line 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci Example: 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci ok 1 - test 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci Parameters: 70862306a36Sopenharmony_ci lines - LineStream of KTAP output to parse 70962306a36Sopenharmony_ci expected_num - expected test number for test to be parsed 71062306a36Sopenharmony_ci log - list of strings containing any preceding diagnostic lines 71162306a36Sopenharmony_ci corresponding to the current test 71262306a36Sopenharmony_ci is_subtest - boolean indicating whether test is a subtest 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci Return: 71562306a36Sopenharmony_ci Test object populated with characteristics and any subtests 71662306a36Sopenharmony_ci """ 71762306a36Sopenharmony_ci test = Test() 71862306a36Sopenharmony_ci test.log.extend(log) 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci # Parse any errors prior to parsing tests 72162306a36Sopenharmony_ci err_log = parse_diagnostic(lines) 72262306a36Sopenharmony_ci test.log.extend(err_log) 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci if not is_subtest: 72562306a36Sopenharmony_ci # If parsing the main/top-level test, parse KTAP version line and 72662306a36Sopenharmony_ci # test plan 72762306a36Sopenharmony_ci test.name = "main" 72862306a36Sopenharmony_ci ktap_line = parse_ktap_header(lines, test) 72962306a36Sopenharmony_ci test.log.extend(parse_diagnostic(lines)) 73062306a36Sopenharmony_ci parse_test_plan(lines, test) 73162306a36Sopenharmony_ci parent_test = True 73262306a36Sopenharmony_ci else: 73362306a36Sopenharmony_ci # If not the main test, attempt to parse a test header containing 73462306a36Sopenharmony_ci # the KTAP version line and/or subtest header line 73562306a36Sopenharmony_ci ktap_line = parse_ktap_header(lines, test) 73662306a36Sopenharmony_ci subtest_line = parse_test_header(lines, test) 73762306a36Sopenharmony_ci parent_test = (ktap_line or subtest_line) 73862306a36Sopenharmony_ci if parent_test: 73962306a36Sopenharmony_ci # If KTAP version line and/or subtest header is found, attempt 74062306a36Sopenharmony_ci # to parse test plan and print test header 74162306a36Sopenharmony_ci test.log.extend(parse_diagnostic(lines)) 74262306a36Sopenharmony_ci parse_test_plan(lines, test) 74362306a36Sopenharmony_ci print_test_header(test) 74462306a36Sopenharmony_ci expected_count = test.expected_count 74562306a36Sopenharmony_ci subtests = [] 74662306a36Sopenharmony_ci test_num = 1 74762306a36Sopenharmony_ci while parent_test and (expected_count is None or test_num <= expected_count): 74862306a36Sopenharmony_ci # Loop to parse any subtests. 74962306a36Sopenharmony_ci # Break after parsing expected number of tests or 75062306a36Sopenharmony_ci # if expected number of tests is unknown break when test 75162306a36Sopenharmony_ci # result line with matching name to subtest header is found 75262306a36Sopenharmony_ci # or no more lines in stream. 75362306a36Sopenharmony_ci sub_log = parse_diagnostic(lines) 75462306a36Sopenharmony_ci sub_test = Test() 75562306a36Sopenharmony_ci if not lines or (peek_test_name_match(lines, test) and 75662306a36Sopenharmony_ci is_subtest): 75762306a36Sopenharmony_ci if expected_count and test_num <= expected_count: 75862306a36Sopenharmony_ci # If parser reaches end of test before 75962306a36Sopenharmony_ci # parsing expected number of subtests, print 76062306a36Sopenharmony_ci # crashed subtest and record error 76162306a36Sopenharmony_ci test.add_error('missing expected subtest!') 76262306a36Sopenharmony_ci sub_test.log.extend(sub_log) 76362306a36Sopenharmony_ci test.counts.add_status( 76462306a36Sopenharmony_ci TestStatus.TEST_CRASHED) 76562306a36Sopenharmony_ci print_test_result(sub_test) 76662306a36Sopenharmony_ci else: 76762306a36Sopenharmony_ci test.log.extend(sub_log) 76862306a36Sopenharmony_ci break 76962306a36Sopenharmony_ci else: 77062306a36Sopenharmony_ci sub_test = parse_test(lines, test_num, sub_log, True) 77162306a36Sopenharmony_ci subtests.append(sub_test) 77262306a36Sopenharmony_ci test_num += 1 77362306a36Sopenharmony_ci test.subtests = subtests 77462306a36Sopenharmony_ci if is_subtest: 77562306a36Sopenharmony_ci # If not main test, look for test result line 77662306a36Sopenharmony_ci test.log.extend(parse_diagnostic(lines)) 77762306a36Sopenharmony_ci if test.name != "" and not peek_test_name_match(lines, test): 77862306a36Sopenharmony_ci test.add_error('missing subtest result line!') 77962306a36Sopenharmony_ci else: 78062306a36Sopenharmony_ci parse_test_result(lines, test, expected_num) 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci # Check for there being no subtests within parent test 78362306a36Sopenharmony_ci if parent_test and len(subtests) == 0: 78462306a36Sopenharmony_ci # Don't override a bad status if this test had one reported. 78562306a36Sopenharmony_ci # Assumption: no subtests means CRASHED is from Test.__init__() 78662306a36Sopenharmony_ci if test.status in (TestStatus.TEST_CRASHED, TestStatus.SUCCESS): 78762306a36Sopenharmony_ci print_log(test.log) 78862306a36Sopenharmony_ci test.status = TestStatus.NO_TESTS 78962306a36Sopenharmony_ci test.add_error('0 tests run!') 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci # Add statuses to TestCounts attribute in Test object 79262306a36Sopenharmony_ci bubble_up_test_results(test) 79362306a36Sopenharmony_ci if parent_test and is_subtest: 79462306a36Sopenharmony_ci # If test has subtests and is not the main test object, print 79562306a36Sopenharmony_ci # footer. 79662306a36Sopenharmony_ci print_test_footer(test) 79762306a36Sopenharmony_ci elif is_subtest: 79862306a36Sopenharmony_ci print_test_result(test) 79962306a36Sopenharmony_ci return test 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_cidef parse_run_tests(kernel_output: Iterable[str]) -> Test: 80262306a36Sopenharmony_ci """ 80362306a36Sopenharmony_ci Using kernel output, extract KTAP lines, parse the lines for test 80462306a36Sopenharmony_ci results and print condensed test results and summary line. 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci Parameters: 80762306a36Sopenharmony_ci kernel_output - Iterable object contains lines of kernel output 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci Return: 81062306a36Sopenharmony_ci Test - the main test object with all subtests. 81162306a36Sopenharmony_ci """ 81262306a36Sopenharmony_ci stdout.print_with_timestamp(DIVIDER) 81362306a36Sopenharmony_ci lines = extract_tap_lines(kernel_output) 81462306a36Sopenharmony_ci test = Test() 81562306a36Sopenharmony_ci if not lines: 81662306a36Sopenharmony_ci test.name = '<missing>' 81762306a36Sopenharmony_ci test.add_error('Could not find any KTAP output. Did any KUnit tests run?') 81862306a36Sopenharmony_ci test.status = TestStatus.FAILURE_TO_PARSE_TESTS 81962306a36Sopenharmony_ci else: 82062306a36Sopenharmony_ci test = parse_test(lines, 0, [], False) 82162306a36Sopenharmony_ci if test.status != TestStatus.NO_TESTS: 82262306a36Sopenharmony_ci test.status = test.counts.get_status() 82362306a36Sopenharmony_ci stdout.print_with_timestamp(DIVIDER) 82462306a36Sopenharmony_ci print_summary_line(test) 82562306a36Sopenharmony_ci return test 826