1from __future__ import annotations 2 3import os 4import subprocess 5from typing import IO, Tuple 6 7from ..oss.utils import get_pytorch_folder 8from ..util.setting import SUMMARY_FOLDER_DIR, TestList, TestStatusType 9 10 11CoverageItem = Tuple[str, float, int, int] 12 13 14def key_by_percentage(x: CoverageItem) -> float: 15 return x[1] 16 17 18def key_by_name(x: CoverageItem) -> str: 19 return x[0] 20 21 22def is_intrested_file(file_path: str, interested_folders: list[str]) -> bool: 23 if "cuda" in file_path: 24 return False 25 if "aten/gen_aten" in file_path or "aten/aten_" in file_path: 26 return False 27 for folder in interested_folders: 28 if folder in file_path: 29 return True 30 return False 31 32 33def is_this_type_of_tests(target_name: str, test_set_by_type: set[str]) -> bool: 34 # tests are divided into three types: success / partial success / fail to collect coverage 35 for test in test_set_by_type: 36 if target_name in test: 37 return True 38 return False 39 40 41def print_test_by_type( 42 tests: TestList, test_set_by_type: set[str], type_name: str, summary_file: IO[str] 43) -> None: 44 print("Tests " + type_name + " to collect coverage:", file=summary_file) 45 for test in tests: 46 if is_this_type_of_tests(test.name, test_set_by_type): 47 print(test.target_pattern, file=summary_file) 48 print(file=summary_file) 49 50 51def print_test_condition( 52 tests: TestList, 53 tests_type: TestStatusType, 54 interested_folders: list[str], 55 coverage_only: list[str], 56 summary_file: IO[str], 57 summary_type: str, 58) -> None: 59 print_test_by_type(tests, tests_type["success"], "fully success", summary_file) 60 print_test_by_type(tests, tests_type["partial"], "partially success", summary_file) 61 print_test_by_type(tests, tests_type["fail"], "failed", summary_file) 62 print( 63 "\n\nCoverage Collected Over Interested Folders:\n", 64 interested_folders, 65 file=summary_file, 66 ) 67 print( 68 "\n\nCoverage Compilation Flags Only Apply To: \n", 69 coverage_only, 70 file=summary_file, 71 ) 72 print( 73 "\n\n---------------------------------- " 74 + summary_type 75 + " ----------------------------------", 76 file=summary_file, 77 ) 78 79 80def line_oriented_report( 81 tests: TestList, 82 tests_type: TestStatusType, 83 interested_folders: list[str], 84 coverage_only: list[str], 85 covered_lines: dict[str, set[int]], 86 uncovered_lines: dict[str, set[int]], 87) -> None: 88 with open(os.path.join(SUMMARY_FOLDER_DIR, "line_summary"), "w+") as report_file: 89 print_test_condition( 90 tests, 91 tests_type, 92 interested_folders, 93 coverage_only, 94 report_file, 95 "LINE SUMMARY", 96 ) 97 for file_name in covered_lines: 98 covered = covered_lines[file_name] 99 uncovered = uncovered_lines[file_name] 100 print( 101 f"{file_name}\n covered lines: {sorted(covered)}\n unconvered lines:{sorted(uncovered)}", 102 file=report_file, 103 ) 104 105 106def print_file_summary( 107 covered_summary: int, total_summary: int, summary_file: IO[str] 108) -> float: 109 # print summary first 110 try: 111 coverage_percentage = 100.0 * covered_summary / total_summary 112 except ZeroDivisionError: 113 coverage_percentage = 0 114 print( 115 f"SUMMARY\ncovered: {covered_summary}\nuncovered: {total_summary}\npercentage: {coverage_percentage:.2f}%\n\n", 116 file=summary_file, 117 ) 118 if coverage_percentage == 0: 119 print("Coverage is 0, Please check if json profiles are valid") 120 return coverage_percentage 121 122 123def print_file_oriented_report( 124 tests_type: TestStatusType, 125 coverage: list[CoverageItem], 126 covered_summary: int, 127 total_summary: int, 128 summary_file: IO[str], 129 tests: TestList, 130 interested_folders: list[str], 131 coverage_only: list[str], 132) -> None: 133 coverage_percentage = print_file_summary( 134 covered_summary, total_summary, summary_file 135 ) 136 # print test condition (interested folder / tests that are successsful or failed) 137 print_test_condition( 138 tests, 139 tests_type, 140 interested_folders, 141 coverage_only, 142 summary_file, 143 "FILE SUMMARY", 144 ) 145 # print each file's information 146 for item in coverage: 147 print( 148 item[0].ljust(75), 149 (str(item[1]) + "%").rjust(10), 150 str(item[2]).rjust(10), 151 str(item[3]).rjust(10), 152 file=summary_file, 153 ) 154 155 print(f"summary percentage:{coverage_percentage:.2f}%") 156 157 158def file_oriented_report( 159 tests: TestList, 160 tests_type: TestStatusType, 161 interested_folders: list[str], 162 coverage_only: list[str], 163 covered_lines: dict[str, set[int]], 164 uncovered_lines: dict[str, set[int]], 165) -> None: 166 with open(os.path.join(SUMMARY_FOLDER_DIR, "file_summary"), "w+") as summary_file: 167 covered_summary = 0 168 total_summary = 0 169 coverage = [] 170 for file_name in covered_lines: 171 # get coverage number for this file 172 covered_count = len(covered_lines[file_name]) 173 total_count = covered_count + len(uncovered_lines[file_name]) 174 try: 175 percentage = round(covered_count / total_count * 100, 2) 176 except ZeroDivisionError: 177 percentage = 0 178 # store information in a list to be sorted 179 coverage.append((file_name, percentage, covered_count, total_count)) 180 # update summary 181 covered_summary = covered_summary + covered_count 182 total_summary = total_summary + total_count 183 # sort 184 coverage.sort(key=key_by_name) 185 coverage.sort(key=key_by_percentage) 186 # print 187 print_file_oriented_report( 188 tests_type, 189 coverage, 190 covered_summary, 191 total_summary, 192 summary_file, 193 tests, 194 interested_folders, 195 coverage_only, 196 ) 197 198 199def get_html_ignored_pattern() -> list[str]: 200 return ["/usr/*", "*anaconda3/*", "*third_party/*"] 201 202 203def html_oriented_report() -> None: 204 # use lcov to generate the coverage report 205 build_folder = os.path.join(get_pytorch_folder(), "build") 206 coverage_info_file = os.path.join(SUMMARY_FOLDER_DIR, "coverage.info") 207 # generage coverage report -- coverage.info in build folder 208 subprocess.check_call( 209 [ 210 "lcov", 211 "--capture", 212 "--directory", 213 build_folder, 214 "--output-file", 215 coverage_info_file, 216 ] 217 ) 218 # remove files that are unrelated 219 cmd_array = ( 220 ["lcov", "--remove", coverage_info_file] 221 + get_html_ignored_pattern() 222 + ["--output-file", coverage_info_file] 223 ) 224 subprocess.check_call( 225 # ["lcov", "--remove", coverage_info_file, "--output-file", coverage_info_file] 226 cmd_array 227 ) 228 # generate beautiful html page 229 subprocess.check_call( 230 [ 231 "genhtml", 232 coverage_info_file, 233 "--output-directory", 234 os.path.join(SUMMARY_FOLDER_DIR, "html_report"), 235 ] 236 ) 237