Skip to content
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
4 changes: 4 additions & 0 deletions diff_cover/git_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ def relative_path(cls, git_diff_path):
"""
Returns git_diff_path relative to cwd.
"""
# If GitPathTool hasn't been initialized, return the path unchanged
if cls._cwd is None or cls._root is None:
return git_diff_path

# Remove git_root from src_path for searching the correct filename
# If cwd is `/home/user/work/diff-cover/diff_cover`
# and src_path is `diff_cover/violations_reporter.py`
Expand Down
20 changes: 13 additions & 7 deletions diff_cover/violationsreporters/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from collections import defaultdict, namedtuple

from diff_cover.command_runner import execute, run_command_for_code
from diff_cover.git_path import GitPathTool
from diff_cover.util import to_unix_path

Violation = namedtuple("Violation", "line, message")
Expand Down Expand Up @@ -152,14 +153,19 @@ def violations(self, src_path):
if not any(src_path.endswith(ext) for ext in self.driver.supported_extensions):
return []

if src_path not in self.violations_dict:
# `src_path` is relative to the git root. We convert it to be relative to
# the current working directory, since quality tools report paths relative
# to the current working directory.
relative_src_path = to_unix_path(GitPathTool.relative_path(src_path))

if relative_src_path not in self.violations_dict:
if self.reports:
self.violations_dict = self.driver.parse_reports(self.reports)
return self.violations_dict[src_path]
return self.violations_dict[relative_src_path]

if not os.path.exists(src_path):
self.violations_dict[src_path] = []
return self.violations_dict[src_path]
if not os.path.exists(relative_src_path):
self.violations_dict[relative_src_path] = []
return self.violations_dict[relative_src_path]

if self.driver_tool_installed is None:
self.driver_tool_installed = self.driver.installed()
Expand All @@ -170,13 +176,13 @@ def violations(self, src_path):
if self.options:
for arg in self.options.split():
command.append(arg)
command.append(src_path.encode(sys.getfilesystemencoding()))
command.append(relative_src_path.encode(sys.getfilesystemencoding()))

stdout, stderr = execute(command, self.driver.exit_codes)
output = stderr if self.driver.output_stderr else stdout
self.violations_dict.update(self.driver.parse_reports([output]))

return self.violations_dict[src_path]
return self.violations_dict[relative_src_path]

def measured_lines(self, src_path):
"""
Expand Down
17 changes: 17 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest

from diff_cover.git_path import GitPathTool


@pytest.fixture(autouse=True)
def reset_git_path_tool():
"""Reset GitPathTool before each test to ensure test isolation.

GitPathTool uses class variables (_cwd and _root) that persist across tests.
This fixture ensures each test starts with a clean state.
"""
GitPathTool._cwd = None
GitPathTool._root = None
yield
GitPathTool._cwd = None
GitPathTool._root = None
43 changes: 43 additions & 0 deletions tests/test_violations_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2312,3 +2312,46 @@ def test_parse_report(self):
driver = ClangFormatDriver()
actual_violations = driver.parse_reports([report])
assert actual_violations == expected_violations


class TestQualityReporterSubdirectory:
"""
Test that QualityReporter works correctly when running from a subdirectory.

When running diff-quality from a subdirectory:
- Git reports paths relative to the git root (e.g., "subdir/file.py")
- Quality tools report paths relative to the current working directory (e.g., "file.py")
"""

def test_violations_from_subdirectory(self, mocker, process_patcher):
"""
Test that violations are found when running from a subdirectory.

Simulates running diff-quality from "subdir/" where:
- Git reports the file as "subdir/file.py" (relative to git root)
- The quality tool reports violations on "file.py" (relative to cwd)
"""
from diff_cover.git_path import GitPathTool

# Simulate running from a subdirectory by mocking relative_path
# to strip the "subdir/" prefix (as it would when cwd is inside subdir/)
mocker.patch.object(
GitPathTool, "relative_path", side_effect=lambda x: x.replace("subdir/", "")
)

# Quality tool outputs violations with paths relative to cwd
# (without the "subdir/" prefix)
tool_output = "file.py:10: error: Something is wrong [error-code]"
process_patcher((tool_output.encode("utf-8"), b""))

quality = QualityReporter(mypy_driver)

# Request violations using the git-relative path (with "subdir/" prefix)
# This is what diff-quality would pass based on git diff output
violations = quality.violations("subdir/file.py")

# Verify violations are found (the fix makes this work)
expected = [
Violation(line=10, message="error: Something is wrong [error-code]")
]
assert violations == expected
Loading