1# Copyright 2021 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""This is a library for printing test result as specified by 5//docs/testing/test_executable_api.md (e.g. --isolated-script-test-output) 6and //docs/testing/json_test_results_format.md 7 8Typical usage: 9 import argparse 10 import test_results 11 12 cmdline_parser = argparse.ArgumentParser() 13 test_results.add_cmdline_args(cmdline_parser) 14 ... adding other cmdline parameter definitions ... 15 parsed_cmdline_args = cmdline_parser.parse_args() 16 17 test_results = [] 18 test_results.append(test_results.TestResult( 19 'test-suite/test-name', 'PASS')) 20 ... 21 22 test_results.print_test_results(parsed_cmdline_args, test_results) 23""" 24 25import argparse 26import json 27import os 28 29 30class TestResult: 31 """TestResult represents a result of executing a single test once. 32 """ 33 34 def __init__(self, 35 test_name, 36 actual_test_result, 37 expected_test_result='PASS'): 38 self.test_name = test_name 39 self.actual_test_result = actual_test_result 40 self.expected_test_result = expected_test_result 41 42 def __eq__(self, other): 43 self_tuple = tuple(sorted(self.__dict__.items())) 44 other_tuple = tuple(sorted(other.__dict__.items())) 45 return self_tuple == other_tuple 46 47 def __hash__(self): 48 return hash(tuple(sorted(self.__dict__.items()))) 49 50 def __repr__(self): 51 result = 'TestResult[{}: {}'.format(self.test_name, 52 self.actual_test_result) 53 if self.expected_test_result != 'PASS': 54 result += ' (expecting: {})]'.format(self.expected_test_result) 55 else: 56 result += ']' 57 return result 58 59 60def _validate_output_directory(outdir): 61 if not os.path.isdir(outdir): 62 raise argparse.ArgumentTypeError('No such directory: ' + outdir) 63 return outdir 64 65 66def add_cmdline_args(argparse_parser): 67 """Adds test-result-specific cmdline parameter definitions to 68 `argparse_parser`. 69 70 Args: 71 argparse_parser: An object of argparse.ArgumentParser type. 72 """ 73 outdir_help = 'Directory where test results will be written into.' 74 argparse_parser.add_argument('--isolated-outdir', 75 dest='outdir', 76 help=outdir_help, 77 metavar='DIRPATH', 78 type=_validate_output_directory) 79 80 outfile_help = 'If this argument is provided, then test results in the ' \ 81 'JSON Test Results Format will be written here. See also ' \ 82 '//docs/testing/json_test_results_format.md' 83 argparse_parser.add_argument('--isolated-script-test-output', 84 dest='test_output', 85 default=None, 86 help=outfile_help, 87 metavar='FILENAME') 88 89 argparse_parser.add_argument('--isolated-script-test-perf-output', 90 dest='ignored_perf_output', 91 default=None, 92 help='Deprecated and ignored.', 93 metavar='IGNORED') 94 95 96def _build_json_data(list_of_test_results, seconds_since_epoch): 97 num_failures_by_type = {} 98 tests = {} 99 for res in list_of_test_results: 100 old_count = num_failures_by_type.get(res.actual_test_result, 0) 101 num_failures_by_type[res.actual_test_result] = old_count + 1 102 103 path = res.test_name.split('//') 104 group = tests 105 for group_name in path[:-1]: 106 if not group_name in group: 107 group[group_name] = {} 108 group = group[group_name] 109 110 group[path[-1]] = { 111 'expected': res.expected_test_result, 112 'actual': res.actual_test_result, 113 } 114 115 return { 116 'interrupted': False, 117 'path_delimiter': '//', 118 'seconds_since_epoch': seconds_since_epoch, 119 'version': 3, 120 'tests': tests, 121 'num_failures_by_type': num_failures_by_type, 122 } 123 124 125def print_test_results(argparse_parsed_args, list_of_test_results, 126 testing_start_time_as_seconds_since_epoch): 127 """Prints `list_of_test_results` to a file specified on the cmdline. 128 129 Args: 130 argparse_parsed_arg: A result of an earlier call to 131 argparse_parser.parse_args() call (where `argparse_parser` has been 132 populated via an even earlier call to add_cmdline_args). 133 list_of_test_results: A list of TestResult objects. 134 testing_start_time_as_seconds_since_epoch: A number from an earlier 135 `time.time()` call. 136 """ 137 if argparse_parsed_args.test_output is None: 138 return 139 140 json_data = _build_json_data(list_of_test_results, 141 testing_start_time_as_seconds_since_epoch) 142 143 filepath = argparse_parsed_args.test_output 144 with open(filepath, mode='w', encoding='utf-8') as f: 145 json.dump(json_data, f, indent=2) 146