18c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci# 38c2ecf20Sopenharmony_ci# Parses test results from a kernel dmesg log. 48c2ecf20Sopenharmony_ci# 58c2ecf20Sopenharmony_ci# Copyright (C) 2019, Google LLC. 68c2ecf20Sopenharmony_ci# Author: Felix Guo <felixguoxiuping@gmail.com> 78c2ecf20Sopenharmony_ci# Author: Brendan Higgins <brendanhiggins@google.com> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ciimport re 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_cifrom collections import namedtuple 128c2ecf20Sopenharmony_cifrom datetime import datetime 138c2ecf20Sopenharmony_cifrom enum import Enum, auto 148c2ecf20Sopenharmony_cifrom functools import reduce 158c2ecf20Sopenharmony_cifrom typing import List, Optional, Tuple 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ciTestResult = namedtuple('TestResult', ['status','suites','log']) 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ciclass TestSuite(object): 208c2ecf20Sopenharmony_ci def __init__(self): 218c2ecf20Sopenharmony_ci self.status = None 228c2ecf20Sopenharmony_ci self.name = None 238c2ecf20Sopenharmony_ci self.cases = [] 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci def __str__(self): 268c2ecf20Sopenharmony_ci return 'TestSuite(' + self.status + ',' + self.name + ',' + str(self.cases) + ')' 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci def __repr__(self): 298c2ecf20Sopenharmony_ci return str(self) 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ciclass TestCase(object): 328c2ecf20Sopenharmony_ci def __init__(self): 338c2ecf20Sopenharmony_ci self.status = None 348c2ecf20Sopenharmony_ci self.name = '' 358c2ecf20Sopenharmony_ci self.log = [] 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci def __str__(self): 388c2ecf20Sopenharmony_ci return 'TestCase(' + self.status + ',' + self.name + ',' + str(self.log) + ')' 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci def __repr__(self): 418c2ecf20Sopenharmony_ci return str(self) 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ciclass TestStatus(Enum): 448c2ecf20Sopenharmony_ci SUCCESS = auto() 458c2ecf20Sopenharmony_ci FAILURE = auto() 468c2ecf20Sopenharmony_ci TEST_CRASHED = auto() 478c2ecf20Sopenharmony_ci NO_TESTS = auto() 488c2ecf20Sopenharmony_ci FAILURE_TO_PARSE_TESTS = auto() 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cikunit_start_re = re.compile(r'TAP version [0-9]+$') 518c2ecf20Sopenharmony_cikunit_end_re = re.compile('(List of all partitions:|' 528c2ecf20Sopenharmony_ci 'Kernel panic - not syncing: VFS:)') 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cidef isolate_kunit_output(kernel_output): 558c2ecf20Sopenharmony_ci started = False 568c2ecf20Sopenharmony_ci for line in kernel_output: 578c2ecf20Sopenharmony_ci line = line.rstrip() # line always has a trailing \n 588c2ecf20Sopenharmony_ci if kunit_start_re.search(line): 598c2ecf20Sopenharmony_ci prefix_len = len(line.split('TAP version')[0]) 608c2ecf20Sopenharmony_ci started = True 618c2ecf20Sopenharmony_ci yield line[prefix_len:] if prefix_len > 0 else line 628c2ecf20Sopenharmony_ci elif kunit_end_re.search(line): 638c2ecf20Sopenharmony_ci break 648c2ecf20Sopenharmony_ci elif started: 658c2ecf20Sopenharmony_ci yield line[prefix_len:] if prefix_len > 0 else line 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cidef raw_output(kernel_output): 688c2ecf20Sopenharmony_ci for line in kernel_output: 698c2ecf20Sopenharmony_ci print(line.rstrip()) 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ciDIVIDER = '=' * 60 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ciRESET = '\033[0;0m' 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cidef red(text): 768c2ecf20Sopenharmony_ci return '\033[1;31m' + text + RESET 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cidef yellow(text): 798c2ecf20Sopenharmony_ci return '\033[1;33m' + text + RESET 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cidef green(text): 828c2ecf20Sopenharmony_ci return '\033[1;32m' + text + RESET 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cidef print_with_timestamp(message): 858c2ecf20Sopenharmony_ci print('[%s] %s' % (datetime.now().strftime('%H:%M:%S'), message)) 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cidef format_suite_divider(message): 888c2ecf20Sopenharmony_ci return '======== ' + message + ' ========' 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_cidef print_suite_divider(message): 918c2ecf20Sopenharmony_ci print_with_timestamp(DIVIDER) 928c2ecf20Sopenharmony_ci print_with_timestamp(format_suite_divider(message)) 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cidef print_log(log): 958c2ecf20Sopenharmony_ci for m in log: 968c2ecf20Sopenharmony_ci print_with_timestamp(m) 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ciTAP_ENTRIES = re.compile(r'^(TAP|[\s]*ok|[\s]*not ok|[\s]*[0-9]+\.\.[0-9]+|[\s]*#).*$') 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cidef consume_non_diagnositic(lines: List[str]) -> None: 1018c2ecf20Sopenharmony_ci while lines and not TAP_ENTRIES.match(lines[0]): 1028c2ecf20Sopenharmony_ci lines.pop(0) 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cidef save_non_diagnositic(lines: List[str], test_case: TestCase) -> None: 1058c2ecf20Sopenharmony_ci while lines and not TAP_ENTRIES.match(lines[0]): 1068c2ecf20Sopenharmony_ci test_case.log.append(lines[0]) 1078c2ecf20Sopenharmony_ci lines.pop(0) 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ciOkNotOkResult = namedtuple('OkNotOkResult', ['is_ok','description', 'text']) 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ciOK_NOT_OK_SUBTEST = re.compile(r'^[\s]+(ok|not ok) [0-9]+ - (.*)$') 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ciOK_NOT_OK_MODULE = re.compile(r'^(ok|not ok) ([0-9]+) - (.*)$') 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cidef parse_ok_not_ok_test_case(lines: List[str], test_case: TestCase) -> bool: 1168c2ecf20Sopenharmony_ci save_non_diagnositic(lines, test_case) 1178c2ecf20Sopenharmony_ci if not lines: 1188c2ecf20Sopenharmony_ci test_case.status = TestStatus.TEST_CRASHED 1198c2ecf20Sopenharmony_ci return True 1208c2ecf20Sopenharmony_ci line = lines[0] 1218c2ecf20Sopenharmony_ci match = OK_NOT_OK_SUBTEST.match(line) 1228c2ecf20Sopenharmony_ci while not match and lines: 1238c2ecf20Sopenharmony_ci line = lines.pop(0) 1248c2ecf20Sopenharmony_ci match = OK_NOT_OK_SUBTEST.match(line) 1258c2ecf20Sopenharmony_ci if match: 1268c2ecf20Sopenharmony_ci test_case.log.append(lines.pop(0)) 1278c2ecf20Sopenharmony_ci test_case.name = match.group(2) 1288c2ecf20Sopenharmony_ci if test_case.status == TestStatus.TEST_CRASHED: 1298c2ecf20Sopenharmony_ci return True 1308c2ecf20Sopenharmony_ci if match.group(1) == 'ok': 1318c2ecf20Sopenharmony_ci test_case.status = TestStatus.SUCCESS 1328c2ecf20Sopenharmony_ci else: 1338c2ecf20Sopenharmony_ci test_case.status = TestStatus.FAILURE 1348c2ecf20Sopenharmony_ci return True 1358c2ecf20Sopenharmony_ci else: 1368c2ecf20Sopenharmony_ci return False 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ciSUBTEST_DIAGNOSTIC = re.compile(r'^[\s]+# .*?: (.*)$') 1398c2ecf20Sopenharmony_ciDIAGNOSTIC_CRASH_MESSAGE = 'kunit test case crashed!' 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cidef parse_diagnostic(lines: List[str], test_case: TestCase) -> bool: 1428c2ecf20Sopenharmony_ci save_non_diagnositic(lines, test_case) 1438c2ecf20Sopenharmony_ci if not lines: 1448c2ecf20Sopenharmony_ci return False 1458c2ecf20Sopenharmony_ci line = lines[0] 1468c2ecf20Sopenharmony_ci match = SUBTEST_DIAGNOSTIC.match(line) 1478c2ecf20Sopenharmony_ci if match: 1488c2ecf20Sopenharmony_ci test_case.log.append(lines.pop(0)) 1498c2ecf20Sopenharmony_ci if match.group(1) == DIAGNOSTIC_CRASH_MESSAGE: 1508c2ecf20Sopenharmony_ci test_case.status = TestStatus.TEST_CRASHED 1518c2ecf20Sopenharmony_ci return True 1528c2ecf20Sopenharmony_ci else: 1538c2ecf20Sopenharmony_ci return False 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_cidef parse_test_case(lines: List[str]) -> Optional[TestCase]: 1568c2ecf20Sopenharmony_ci test_case = TestCase() 1578c2ecf20Sopenharmony_ci save_non_diagnositic(lines, test_case) 1588c2ecf20Sopenharmony_ci while parse_diagnostic(lines, test_case): 1598c2ecf20Sopenharmony_ci pass 1608c2ecf20Sopenharmony_ci if parse_ok_not_ok_test_case(lines, test_case): 1618c2ecf20Sopenharmony_ci return test_case 1628c2ecf20Sopenharmony_ci else: 1638c2ecf20Sopenharmony_ci return None 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ciSUBTEST_HEADER = re.compile(r'^[\s]+# Subtest: (.*)$') 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cidef parse_subtest_header(lines: List[str]) -> Optional[str]: 1688c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 1698c2ecf20Sopenharmony_ci if not lines: 1708c2ecf20Sopenharmony_ci return None 1718c2ecf20Sopenharmony_ci match = SUBTEST_HEADER.match(lines[0]) 1728c2ecf20Sopenharmony_ci if match: 1738c2ecf20Sopenharmony_ci lines.pop(0) 1748c2ecf20Sopenharmony_ci return match.group(1) 1758c2ecf20Sopenharmony_ci else: 1768c2ecf20Sopenharmony_ci return None 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ciSUBTEST_PLAN = re.compile(r'[\s]+[0-9]+\.\.([0-9]+)') 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cidef parse_subtest_plan(lines: List[str]) -> Optional[int]: 1818c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 1828c2ecf20Sopenharmony_ci match = SUBTEST_PLAN.match(lines[0]) 1838c2ecf20Sopenharmony_ci if match: 1848c2ecf20Sopenharmony_ci lines.pop(0) 1858c2ecf20Sopenharmony_ci return int(match.group(1)) 1868c2ecf20Sopenharmony_ci else: 1878c2ecf20Sopenharmony_ci return None 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_cidef max_status(left: TestStatus, right: TestStatus) -> TestStatus: 1908c2ecf20Sopenharmony_ci if left == TestStatus.TEST_CRASHED or right == TestStatus.TEST_CRASHED: 1918c2ecf20Sopenharmony_ci return TestStatus.TEST_CRASHED 1928c2ecf20Sopenharmony_ci elif left == TestStatus.FAILURE or right == TestStatus.FAILURE: 1938c2ecf20Sopenharmony_ci return TestStatus.FAILURE 1948c2ecf20Sopenharmony_ci elif left != TestStatus.SUCCESS: 1958c2ecf20Sopenharmony_ci return left 1968c2ecf20Sopenharmony_ci elif right != TestStatus.SUCCESS: 1978c2ecf20Sopenharmony_ci return right 1988c2ecf20Sopenharmony_ci else: 1998c2ecf20Sopenharmony_ci return TestStatus.SUCCESS 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cidef parse_ok_not_ok_test_suite(lines: List[str], 2028c2ecf20Sopenharmony_ci test_suite: TestSuite, 2038c2ecf20Sopenharmony_ci expected_suite_index: int) -> bool: 2048c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 2058c2ecf20Sopenharmony_ci if not lines: 2068c2ecf20Sopenharmony_ci test_suite.status = TestStatus.TEST_CRASHED 2078c2ecf20Sopenharmony_ci return False 2088c2ecf20Sopenharmony_ci line = lines[0] 2098c2ecf20Sopenharmony_ci match = OK_NOT_OK_MODULE.match(line) 2108c2ecf20Sopenharmony_ci if match: 2118c2ecf20Sopenharmony_ci lines.pop(0) 2128c2ecf20Sopenharmony_ci if match.group(1) == 'ok': 2138c2ecf20Sopenharmony_ci test_suite.status = TestStatus.SUCCESS 2148c2ecf20Sopenharmony_ci else: 2158c2ecf20Sopenharmony_ci test_suite.status = TestStatus.FAILURE 2168c2ecf20Sopenharmony_ci suite_index = int(match.group(2)) 2178c2ecf20Sopenharmony_ci if suite_index != expected_suite_index: 2188c2ecf20Sopenharmony_ci print_with_timestamp( 2198c2ecf20Sopenharmony_ci red('[ERROR] ') + 'expected_suite_index ' + 2208c2ecf20Sopenharmony_ci str(expected_suite_index) + ', but got ' + 2218c2ecf20Sopenharmony_ci str(suite_index)) 2228c2ecf20Sopenharmony_ci return True 2238c2ecf20Sopenharmony_ci else: 2248c2ecf20Sopenharmony_ci return False 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cidef bubble_up_errors(to_status, status_container_list) -> TestStatus: 2278c2ecf20Sopenharmony_ci status_list = map(to_status, status_container_list) 2288c2ecf20Sopenharmony_ci return reduce(max_status, status_list, TestStatus.SUCCESS) 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cidef bubble_up_test_case_errors(test_suite: TestSuite) -> TestStatus: 2318c2ecf20Sopenharmony_ci max_test_case_status = bubble_up_errors(lambda x: x.status, test_suite.cases) 2328c2ecf20Sopenharmony_ci return max_status(max_test_case_status, test_suite.status) 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cidef parse_test_suite(lines: List[str], expected_suite_index: int) -> Optional[TestSuite]: 2358c2ecf20Sopenharmony_ci if not lines: 2368c2ecf20Sopenharmony_ci return None 2378c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 2388c2ecf20Sopenharmony_ci test_suite = TestSuite() 2398c2ecf20Sopenharmony_ci test_suite.status = TestStatus.SUCCESS 2408c2ecf20Sopenharmony_ci name = parse_subtest_header(lines) 2418c2ecf20Sopenharmony_ci if not name: 2428c2ecf20Sopenharmony_ci return None 2438c2ecf20Sopenharmony_ci test_suite.name = name 2448c2ecf20Sopenharmony_ci expected_test_case_num = parse_subtest_plan(lines) 2458c2ecf20Sopenharmony_ci if expected_test_case_num is None: 2468c2ecf20Sopenharmony_ci return None 2478c2ecf20Sopenharmony_ci while expected_test_case_num > 0: 2488c2ecf20Sopenharmony_ci test_case = parse_test_case(lines) 2498c2ecf20Sopenharmony_ci if not test_case: 2508c2ecf20Sopenharmony_ci break 2518c2ecf20Sopenharmony_ci test_suite.cases.append(test_case) 2528c2ecf20Sopenharmony_ci expected_test_case_num -= 1 2538c2ecf20Sopenharmony_ci if parse_ok_not_ok_test_suite(lines, test_suite, expected_suite_index): 2548c2ecf20Sopenharmony_ci test_suite.status = bubble_up_test_case_errors(test_suite) 2558c2ecf20Sopenharmony_ci return test_suite 2568c2ecf20Sopenharmony_ci elif not lines: 2578c2ecf20Sopenharmony_ci print_with_timestamp(red('[ERROR] ') + 'ran out of lines before end token') 2588c2ecf20Sopenharmony_ci return test_suite 2598c2ecf20Sopenharmony_ci else: 2608c2ecf20Sopenharmony_ci print('failed to parse end of suite' + lines[0]) 2618c2ecf20Sopenharmony_ci return None 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ciTAP_HEADER = re.compile(r'^TAP version 14$') 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cidef parse_tap_header(lines: List[str]) -> bool: 2668c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 2678c2ecf20Sopenharmony_ci if TAP_HEADER.match(lines[0]): 2688c2ecf20Sopenharmony_ci lines.pop(0) 2698c2ecf20Sopenharmony_ci return True 2708c2ecf20Sopenharmony_ci else: 2718c2ecf20Sopenharmony_ci return False 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ciTEST_PLAN = re.compile(r'[0-9]+\.\.([0-9]+)') 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_cidef parse_test_plan(lines: List[str]) -> Optional[int]: 2768c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 2778c2ecf20Sopenharmony_ci match = TEST_PLAN.match(lines[0]) 2788c2ecf20Sopenharmony_ci if match: 2798c2ecf20Sopenharmony_ci lines.pop(0) 2808c2ecf20Sopenharmony_ci return int(match.group(1)) 2818c2ecf20Sopenharmony_ci else: 2828c2ecf20Sopenharmony_ci return None 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cidef bubble_up_suite_errors(test_suite_list: List[TestSuite]) -> TestStatus: 2858c2ecf20Sopenharmony_ci return bubble_up_errors(lambda x: x.status, test_suite_list) 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cidef parse_test_result(lines: List[str]) -> TestResult: 2888c2ecf20Sopenharmony_ci consume_non_diagnositic(lines) 2898c2ecf20Sopenharmony_ci if not lines or not parse_tap_header(lines): 2908c2ecf20Sopenharmony_ci return TestResult(TestStatus.NO_TESTS, [], lines) 2918c2ecf20Sopenharmony_ci expected_test_suite_num = parse_test_plan(lines) 2928c2ecf20Sopenharmony_ci if not expected_test_suite_num: 2938c2ecf20Sopenharmony_ci return TestResult(TestStatus.FAILURE_TO_PARSE_TESTS, [], lines) 2948c2ecf20Sopenharmony_ci test_suites = [] 2958c2ecf20Sopenharmony_ci for i in range(1, expected_test_suite_num + 1): 2968c2ecf20Sopenharmony_ci test_suite = parse_test_suite(lines, i) 2978c2ecf20Sopenharmony_ci if test_suite: 2988c2ecf20Sopenharmony_ci test_suites.append(test_suite) 2998c2ecf20Sopenharmony_ci else: 3008c2ecf20Sopenharmony_ci print_with_timestamp( 3018c2ecf20Sopenharmony_ci red('[ERROR] ') + ' expected ' + 3028c2ecf20Sopenharmony_ci str(expected_test_suite_num) + 3038c2ecf20Sopenharmony_ci ' test suites, but got ' + str(i - 2)) 3048c2ecf20Sopenharmony_ci break 3058c2ecf20Sopenharmony_ci test_suite = parse_test_suite(lines, -1) 3068c2ecf20Sopenharmony_ci if test_suite: 3078c2ecf20Sopenharmony_ci print_with_timestamp(red('[ERROR] ') + 3088c2ecf20Sopenharmony_ci 'got unexpected test suite: ' + test_suite.name) 3098c2ecf20Sopenharmony_ci if test_suites: 3108c2ecf20Sopenharmony_ci return TestResult(bubble_up_suite_errors(test_suites), test_suites, lines) 3118c2ecf20Sopenharmony_ci else: 3128c2ecf20Sopenharmony_ci return TestResult(TestStatus.NO_TESTS, [], lines) 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cidef print_and_count_results(test_result: TestResult) -> Tuple[int, int, int]: 3158c2ecf20Sopenharmony_ci total_tests = 0 3168c2ecf20Sopenharmony_ci failed_tests = 0 3178c2ecf20Sopenharmony_ci crashed_tests = 0 3188c2ecf20Sopenharmony_ci for test_suite in test_result.suites: 3198c2ecf20Sopenharmony_ci if test_suite.status == TestStatus.SUCCESS: 3208c2ecf20Sopenharmony_ci print_suite_divider(green('[PASSED] ') + test_suite.name) 3218c2ecf20Sopenharmony_ci elif test_suite.status == TestStatus.TEST_CRASHED: 3228c2ecf20Sopenharmony_ci print_suite_divider(red('[CRASHED] ' + test_suite.name)) 3238c2ecf20Sopenharmony_ci else: 3248c2ecf20Sopenharmony_ci print_suite_divider(red('[FAILED] ') + test_suite.name) 3258c2ecf20Sopenharmony_ci for test_case in test_suite.cases: 3268c2ecf20Sopenharmony_ci total_tests += 1 3278c2ecf20Sopenharmony_ci if test_case.status == TestStatus.SUCCESS: 3288c2ecf20Sopenharmony_ci print_with_timestamp(green('[PASSED] ') + test_case.name) 3298c2ecf20Sopenharmony_ci elif test_case.status == TestStatus.TEST_CRASHED: 3308c2ecf20Sopenharmony_ci crashed_tests += 1 3318c2ecf20Sopenharmony_ci print_with_timestamp(red('[CRASHED] ' + test_case.name)) 3328c2ecf20Sopenharmony_ci print_log(map(yellow, test_case.log)) 3338c2ecf20Sopenharmony_ci print_with_timestamp('') 3348c2ecf20Sopenharmony_ci else: 3358c2ecf20Sopenharmony_ci failed_tests += 1 3368c2ecf20Sopenharmony_ci print_with_timestamp(red('[FAILED] ') + test_case.name) 3378c2ecf20Sopenharmony_ci print_log(map(yellow, test_case.log)) 3388c2ecf20Sopenharmony_ci print_with_timestamp('') 3398c2ecf20Sopenharmony_ci return total_tests, failed_tests, crashed_tests 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_cidef parse_run_tests(kernel_output) -> TestResult: 3428c2ecf20Sopenharmony_ci total_tests = 0 3438c2ecf20Sopenharmony_ci failed_tests = 0 3448c2ecf20Sopenharmony_ci crashed_tests = 0 3458c2ecf20Sopenharmony_ci test_result = parse_test_result(list(isolate_kunit_output(kernel_output))) 3468c2ecf20Sopenharmony_ci if test_result.status == TestStatus.NO_TESTS: 3478c2ecf20Sopenharmony_ci print(red('[ERROR] ') + yellow('no tests run!')) 3488c2ecf20Sopenharmony_ci elif test_result.status == TestStatus.FAILURE_TO_PARSE_TESTS: 3498c2ecf20Sopenharmony_ci print(red('[ERROR] ') + yellow('could not parse test results!')) 3508c2ecf20Sopenharmony_ci else: 3518c2ecf20Sopenharmony_ci (total_tests, 3528c2ecf20Sopenharmony_ci failed_tests, 3538c2ecf20Sopenharmony_ci crashed_tests) = print_and_count_results(test_result) 3548c2ecf20Sopenharmony_ci print_with_timestamp(DIVIDER) 3558c2ecf20Sopenharmony_ci fmt = green if test_result.status == TestStatus.SUCCESS else red 3568c2ecf20Sopenharmony_ci print_with_timestamp( 3578c2ecf20Sopenharmony_ci fmt('Testing complete. %d tests run. %d failed. %d crashed.' % 3588c2ecf20Sopenharmony_ci (total_tests, failed_tests, crashed_tests))) 3598c2ecf20Sopenharmony_ci return test_result 360