xref: /aosp_15_r20/external/pytorch/tools/code_coverage/package/tool/parser/llvm_coverage_parser.py (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1from __future__ import annotations
2
3from typing import Any
4
5from .coverage_record import CoverageRecord
6from .llvm_coverage_segment import LlvmCoverageSegment, parse_segments
7
8
9class LlvmCoverageParser:
10    """
11    Accepts a parsed json produced by llvm-cov export -- typically,
12    representing a single C++ test and produces a list
13    of CoverageRecord(s).
14
15    """
16
17    def __init__(self, llvm_coverage: dict[str, Any]) -> None:
18        self._llvm_coverage = llvm_coverage
19
20    @staticmethod
21    def _skip_coverage(path: str) -> bool:
22        """
23        Returns True if file path should not be processed.
24        This is repo-specific and only makes sense for the current state of
25        ovrsource.
26        """
27        return "/third-party/" in path
28
29    @staticmethod
30    def _collect_coverage(
31        segments: list[LlvmCoverageSegment],
32    ) -> tuple[list[int], list[int]]:
33        """
34        Stateful parsing of coverage segments.
35        """
36        covered_lines: set[int] = set()
37        uncovered_lines: set[int] = set()
38        prev_segment = LlvmCoverageSegment(1, 0, 0, 0, 0, None)
39        for segment in segments:
40            covered_range, uncovered_range = segment.get_coverage(prev_segment)
41            covered_lines.update(covered_range)
42            uncovered_lines.update(uncovered_range)
43            prev_segment = segment
44
45        uncovered_lines.difference_update(covered_lines)
46        return sorted(covered_lines), sorted(uncovered_lines)
47
48    def parse(self, repo_name: str) -> list[CoverageRecord]:
49        # The JSON format is described in the LLVM source code
50        # https://github.com/llvm-mirror/llvm/blob/master/tools/llvm-cov/CoverageExporterJson.cpp
51        records: list[CoverageRecord] = []
52        for export_unit in self._llvm_coverage["data"]:
53            for file_info in export_unit["files"]:
54                filepath = file_info["filename"]
55                if self._skip_coverage(filepath):
56                    continue
57
58                if filepath is None:
59                    continue
60
61                segments = file_info["segments"]
62
63                covered_lines, uncovered_lines = self._collect_coverage(
64                    parse_segments(segments)
65                )
66
67                records.append(CoverageRecord(filepath, covered_lines, uncovered_lines))
68
69        return records
70