Skip to content

Commit

Permalink
collect coverage for all class elements for filename (#252)
Browse files Browse the repository at this point in the history
* collect coverage for all class elements for filename

* Incrementing version.txt

---------

Co-authored-by: Embedded DevOps <[email protected]>
  • Loading branch information
qododavid and EmbeddedDevops1 authored Dec 24, 2024
1 parent 356a142 commit b4ef3fb
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 16 deletions.
49 changes: 37 additions & 12 deletions cover_agent/CoverageProcessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,35 +109,60 @@ def parse_coverage_report(self) -> Tuple[list, list, float]:

def parse_coverage_report_cobertura(self, filename: str = None) -> Union[Tuple[list, list, float], dict]:
"""
Parses a Cobertura XML code coverage report to extract covered and missed line numbers for a specific file
or all files, and calculates the coverage percentage.
Parses a Cobertura XML code coverage report to extract covered and missed line numbers
for a specific file or for all files (if filename is None). Aggregates coverage data from
multiple <class> entries that share the same filename.
Args:
filename (str, optional): The name of the file to process. If None, processes all files.
filename (str, optional): Filename to process. If None, process all files.
Returns:
Union[Tuple[list, list, float], dict]: If filename is provided, returns a tuple
containing lists of covered and missed line numbers, and the coverage percentage.
If filename is None, returns a dictionary with filenames as keys and a tuple
containing lists of covered and missed line numbers, and the coverage percentage
as values.
If filename is provided, returns (covered_lines, missed_lines, coverage_percent).
If filename is None, returns a dict: { filename: (covered_lines, missed_lines, coverage_percent) }.
"""
tree = ET.parse(self.file_path)
root = tree.getroot()

if filename:
# Collect coverage for all <class> elements matching the given filename
all_covered, all_missed = [], []
for cls in root.findall(".//class"):
name_attr = cls.get("filename")
if name_attr and name_attr.endswith(filename):
return self.parse_coverage_data_for_class(cls)
return [], [], 0.0 # Return empty lists if the file is not found
c_covered, c_missed, _ = self.parse_coverage_data_for_class(cls)
all_covered.extend(c_covered)
all_missed.extend(c_missed)

# Deduplicate and compute coverage
covered_set = set(all_covered)
missed_set = set(all_missed) - covered_set
total_lines = len(covered_set) + len(missed_set)
coverage_percentage = (len(covered_set) / total_lines) if total_lines else 0

return list(covered_set), list(missed_set), coverage_percentage

else:
# Collect coverage for every <class>, grouping by filename
coverage_data = {}
file_map = {} # filename -> ([covered], [missed])

for cls in root.findall(".//class"):
cls_filename = cls.get("filename")
if cls_filename:
lines_covered, lines_missed, coverage_percentage = self.parse_coverage_data_for_class(cls)
coverage_data[cls_filename] = (lines_covered, lines_missed, coverage_percentage)
c_covered, c_missed, _ = self.parse_coverage_data_for_class(cls)
if cls_filename not in file_map:
file_map[cls_filename] = ([], [])
file_map[cls_filename][0].extend(c_covered)
file_map[cls_filename][1].extend(c_missed)

# Convert raw lists to sets, compute coverage, store results
for f_name, (c_covered, c_missed) in file_map.items():
covered_set = set(c_covered)
missed_set = set(c_missed) - covered_set
total_lines = len(covered_set) + len(missed_set)
coverage_percentage = (len(covered_set) / total_lines) if total_lines else 0
coverage_data[f_name] = (list(covered_set), list(missed_set), coverage_percentage)

return coverage_data

def parse_coverage_data_for_class(self, cls) -> Tuple[list, list, float]:
Expand Down
2 changes: 1 addition & 1 deletion cover_agent/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.10
0.2.11
12 changes: 9 additions & 3 deletions tests/test_CoverageProcessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ def mock_parse(file_path):
<line number="2" hits="0"/>
</lines>
</class>
<class filename="app.py">
<lines>
<line number="3" hits="1"/>
<line number="4" hits="0"/>
</lines>
</class>
</classes>
</package>
</packages>
Expand All @@ -43,8 +49,8 @@ def test_parse_coverage_report_cobertura(self, mock_xml_tree, processor):
"""
covered_lines, missed_lines, coverage_pct = processor.parse_coverage_report()

assert covered_lines == [1], "Should list line 1 as covered"
assert missed_lines == [2], "Should list line 2 as missed"
assert covered_lines == [1, 3], "Should list lines 1 and 3 as covered"
assert missed_lines == [2, 4], "Should list lines 2 and 4 as missed"
assert coverage_pct == 0.5, "Coverage should be 50 percent"

def test_correct_parsing_for_matching_package_and_class(self, mocker):
Expand Down Expand Up @@ -487,7 +493,7 @@ def test_parse_coverage_report_lcov_file_read_error(self, mocker):
def test_parse_coverage_report_cobertura_all_files(self, mock_xml_tree, processor):
coverage_data = processor.parse_coverage_report_cobertura()
expected_data = {
"app.py": ([1], [2], 0.5)
"app.py": ([1, 3], [2, 4], 0.5)
}
assert coverage_data == expected_data, "Expected coverage data for all files"

Expand Down

0 comments on commit b4ef3fb

Please sign in to comment.