Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix files only covered by one LCOV report showing 100% coverage #433

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions diff_cover/violationsreporters/violations_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ def _cache_file(self, src_path):
src_search_path = src_abs_path
if src_search_path not in lcov_document:
src_search_path = src_rel_path
if src_search_path not in lcov_document:
continue

# First case, need to define violations initially
if violations is None:
Expand Down
197 changes: 197 additions & 0 deletions tests/test_violations_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import os
import subprocess
import tempfile
import xml.etree.ElementTree as etree
from io import BytesIO, StringIO
from subprocess import Popen
Expand All @@ -18,6 +19,7 @@
from diff_cover.violationsreporters.violations_reporter import (
CppcheckDriver,
EslintDriver,
LcovCoverageReporter,
PylintDriver,
Violation,
XmlCoverageReporter,
Expand Down Expand Up @@ -794,6 +796,201 @@ def _coverage_xml(self, file_paths, violations, measured):
return root


class TestLcovCoverageReporterTest:
MANY_VIOLATIONS = {
Violation(3, None),
Violation(7, None),
Violation(11, None),
Violation(13, None),
}
FEW_MEASURED = {2, 3, 5, 7, 11, 13}

FEW_VIOLATIONS = {Violation(3, None), Violation(11, None)}
MANY_MEASURED = {2, 3, 5, 7, 11, 13, 17}

ONE_VIOLATION = {Violation(11, None)}
VERY_MANY_MEASURED = {2, 3, 5, 7, 11, 13, 17, 23, 24, 25, 26, 26, 27}

@pytest.fixture(autouse=True)
def patch_git_patch(self, mocker):
# Paths generated by git_path are always the given argument
_git_path_mock = mocker.patch(
"diff_cover.violationsreporters.violations_reporter.GitPathTool"
)
_git_path_mock.relative_path = lambda path: path
_git_path_mock.absolute_path = lambda path: path

def test_violations(self):
# Construct the LCOV report
file_paths = ["file1.java", "subdir/file2.java"]
violations = self.MANY_VIOLATIONS
measured = self.FEW_MEASURED
lcov = self._coverage_lcov(file_paths, violations, measured)

# Parse the report
coverage = LcovCoverageReporter([lcov])

# Expect that the name is set
assert coverage.name() == "LCOV"

# By construction, each file has the same set
# of covered/uncovered lines
assert violations == coverage.violations("file1.java")
assert measured == coverage.measured_lines("file1.java")

# Try getting a smaller range
result = coverage.violations("subdir/file2.java")
assert result == violations

# Once more on the first file (for caching)
result = coverage.violations("file1.java")
assert result == violations

def test_two_inputs_first_violate(self):
# Construct the LCOV report
file_paths = ["file1.java"]

violations1 = self.MANY_VIOLATIONS
violations2 = self.FEW_VIOLATIONS

measured1 = self.FEW_MEASURED
measured2 = self.MANY_MEASURED

lcov = self._coverage_lcov(file_paths, violations1, measured1)
lcov2 = self._coverage_lcov(file_paths, violations2, measured2)

# Parse the report
coverage = LcovCoverageReporter([lcov, lcov2])

# By construction, each file has the same set
# of covered/uncovered lines
assert violations1 & violations2 == coverage.violations("file1.java")

assert measured1 | measured2 == coverage.measured_lines("file1.java")

def test_two_inputs_second_violate(self):
# Construct the LCOV report
file_paths = ["file1.java"]

violations1 = self.MANY_VIOLATIONS
violations2 = self.FEW_VIOLATIONS

measured1 = self.FEW_MEASURED
measured2 = self.MANY_MEASURED

lcov = self._coverage_lcov(file_paths, violations1, measured1)
lcov2 = self._coverage_lcov(file_paths, violations2, measured2)

# Parse the report
coverage = LcovCoverageReporter([lcov2, lcov])

# By construction, each file has the same set
# of covered/uncovered lines
assert violations1 & violations2 == coverage.violations("file1.java")

assert measured1 | measured2 == coverage.measured_lines("file1.java")

def test_three_inputs(self):
# Construct the LCOV report
file_paths = ["file1.java"]

violations1 = self.MANY_VIOLATIONS
violations2 = self.FEW_VIOLATIONS
violations3 = self.ONE_VIOLATION

measured1 = self.FEW_MEASURED
measured2 = self.MANY_MEASURED
measured3 = self.VERY_MANY_MEASURED

lcov = self._coverage_lcov(file_paths, violations1, measured1)
lcov2 = self._coverage_lcov(file_paths, violations2, measured2)
lcov3 = self._coverage_lcov(file_paths, violations3, measured3)

# Parse the report
coverage = LcovCoverageReporter([lcov2, lcov, lcov3])

# By construction, each file has the same set
# of covered/uncovered lines
assert violations1 & violations2 & violations3 == coverage.violations(
"file1.java"
)

assert measured1 | measured2 | measured3 == coverage.measured_lines(
"file1.java"
)

def test_different_files_in_inputs(self):
# Construct the LCOV report
lcov_repots = [
self._coverage_lcov(["file.java"], self.MANY_VIOLATIONS, self.FEW_MEASURED),
self._coverage_lcov(
["other_file.java"], self.FEW_VIOLATIONS, self.MANY_MEASURED
),
]

# Parse the report
coverage = LcovCoverageReporter(lcov_repots)

assert self.MANY_VIOLATIONS == coverage.violations("file.java")
assert self.FEW_VIOLATIONS == coverage.violations("other_file.java")

def test_empty_violations(self):
"""
Test that an empty violations report is handled properly
"""
# Construct the LCOV report
file_paths = ["file1.java"]

violations1 = self.MANY_VIOLATIONS
violations2 = set()

measured1 = self.FEW_MEASURED
measured2 = self.MANY_MEASURED

lcov = self._coverage_lcov(file_paths, violations1, measured1)
lcov2 = self._coverage_lcov(file_paths, violations2, measured2)

# Parse the report
coverage = LcovCoverageReporter([lcov2, lcov])

# By construction, each file has the same set
# of covered/uncovered lines
assert violations1 & violations2 == coverage.violations("file1.java")

assert measured1 | measured2 == coverage.measured_lines("file1.java")

def test_no_such_file(self):
# Construct the LCOV report with no source files
lcov = self._coverage_lcov([], [], [])

# Parse the report
coverage = LcovCoverageReporter(lcov)

# Expect that we get no results
result = coverage.violations("file.java")
assert result == set()

def _coverage_lcov(self, file_paths, violations, measured):
"""
Build an LCOV document based on the provided arguments.
"""

violation_lines = {violation.line for violation in violations}

with tempfile.NamedTemporaryFile("w", delete=False) as f:
for file_path in file_paths:
f.write(f"SF:{file_path}\n")
for line_num in measured:
f.write(
f"DA:{line_num},{0 if line_num in violation_lines else 1}\n"
)
f.write("end_of_record\n")
try:
return LcovCoverageReporter.parse(f.name)
finally:
os.unlink(f.name)


class TestPycodestyleQualityReporterTest:
def test_quality(self, mocker, process_patcher):
# Patch the output of `pycodestyle`
Expand Down
Loading