xref: /aosp_15_r20/external/pytorch/tools/code_coverage/package/tool/print_report.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
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