Coverage for tests / integration / test_pydoclint_integration.py: 96%
80 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Integration tests for pydoclint core.
3Note: The simplified pydoclint plugin reads configuration from [tool.pydoclint]
4in pyproject.toml. See docs/tool-analysis/pydoclint-analysis.md for settings.
5"""
7import shutil
8import subprocess
9from pathlib import Path
11import pytest
12from assertpy import assert_that
13from loguru import logger
15from lintro.plugins import ToolRegistry
17logger.remove()
18logger.add(lambda msg: print(msg, end=""), level="INFO")
20SAMPLE_FILE = "test_samples/tools/python/pydoclint/pydoclint_violations.py"
23def run_pydoclint_directly(file_path: Path) -> tuple[bool, str, int]:
24 """Run pydoclint directly on a file and return result tuple.
26 Args:
27 file_path: Path to the file to check with pydoclint.
29 Returns:
30 tuple[bool, str, int]: A tuple of (success, output, issues_count).
31 """
32 cmd = [
33 "pydoclint",
34 "--quiet",
35 str(file_path),
36 ]
37 result = subprocess.run(
38 cmd,
39 capture_output=True,
40 text=True,
41 check=False,
42 )
43 output = result.stdout + result.stderr
44 issues = [line for line in output.splitlines() if "DOC" in line and ":" in line]
45 issues_count = len(issues)
46 success = issues_count == 0 and result.returncode == 0
47 return success, output, issues_count
50def _ensure_pydoclint_available() -> None:
51 """Skip test if pydoclint CLI is not runnable.
53 Attempts to execute `pydoclint --version` to verify that the CLI exists
54 and is runnable in the current environment.
55 """
56 try:
57 result = subprocess.run(
58 ["pydoclint", "--version"],
59 capture_output=True,
60 text=True,
61 check=False,
62 )
63 if result.returncode != 0:
64 pytest.skip("pydoclint CLI not working; skipping direct CLI test")
65 except FileNotFoundError:
66 pytest.skip("pydoclint CLI not installed; skipping direct CLI test")
69def test_pydoclint_reports_violations_direct(tmp_path: Path) -> None:
70 """Pydoclint CLI: Should detect and report violations in a sample file.
72 Args:
73 tmp_path: Pytest temporary directory fixture.
74 """
75 _ensure_pydoclint_available()
76 sample_file = tmp_path / "pydoclint_violations.py"
77 shutil.copy(SAMPLE_FILE, sample_file)
78 logger.info("[TEST] Running pydoclint directly on sample file...")
79 success, output, issues = run_pydoclint_directly(sample_file)
80 logger.info(f"[LOG] Pydoclint found {issues} issues. Output:\n{output}")
81 assert_that(success).is_false().described_as(
82 "Pydoclint should fail when violations are present.",
83 )
84 assert_that(issues).is_greater_than(0).described_as(
85 "Pydoclint should report at least one issue.",
86 )
87 assert_that(output).contains("DOC").described_as(
88 "Pydoclint output should contain error codes.",
89 )
92def test_pydoclint_reports_violations_through_lintro(tmp_path: Path) -> None:
93 """Lintro PydoclintTool: Should detect and report violations in a sample file.
95 Args:
96 tmp_path: Pytest temporary directory fixture.
97 """
98 _ensure_pydoclint_available()
99 sample_file = tmp_path / "pydoclint_violations.py"
100 shutil.copy(SAMPLE_FILE, sample_file)
101 logger.info(f"SAMPLE_FILE: {sample_file}, exists: {sample_file.exists()}")
102 logger.info("[TEST] Running PydoclintTool through lintro on sample file...")
103 tool = ToolRegistry.get("pydoclint")
104 assert_that(tool).is_not_none()
105 result = tool.check([str(sample_file)], {})
106 logger.info(
107 f"[LOG] Lintro PydoclintTool found {result.issues_count} issues. "
108 f"Output:\n{result.output}",
109 )
110 assert_that(result.success).is_false().described_as(
111 "Lintro PydoclintTool should fail when violations are present.",
112 )
113 assert_that(result.issues_count).is_greater_than(0).described_as(
114 "Lintro PydoclintTool should report at least one issue.",
115 )
118def test_pydoclint_output_consistency_direct_vs_lintro(tmp_path: Path) -> None:
119 """Pydoclint CLI vs Lintro: Should produce consistent results for the same file.
121 Args:
122 tmp_path: Pytest temporary directory fixture.
123 """
124 _ensure_pydoclint_available()
125 sample_file = tmp_path / "pydoclint_violations.py"
126 shutil.copy(SAMPLE_FILE, sample_file)
127 logger.info("[TEST] Comparing pydoclint CLI and Lintro PydoclintTool outputs...")
128 tool = ToolRegistry.get("pydoclint")
129 assert_that(tool).is_not_none()
130 direct_success, direct_output, direct_issues = run_pydoclint_directly(sample_file)
131 result = tool.check([str(sample_file)], {})
132 logger.info(
133 f"[LOG] CLI issues: {direct_issues}, Lintro issues: {result.issues_count}",
134 )
135 assert_that(direct_success).is_equal_to(result.success).described_as(
136 "Success/failure mismatch between CLI and Lintro.",
137 )
138 # Issue count may differ slightly due to parsing differences
139 # But both should find issues
140 assert_that(direct_issues).is_greater_than(0)
141 assert_that(result.issues_count).is_greater_than(0)
144def test_pydoclint_fix_method_not_implemented(tmp_path: Path) -> None:
145 """Lintro PydoclintTool: .fix() should raise NotImplementedError.
147 Args:
148 tmp_path: Pytest temporary directory fixture.
149 """
150 sample_file = tmp_path / "pydoclint_violations.py"
151 shutil.copy(SAMPLE_FILE, sample_file)
152 logger.info(
153 "[TEST] Verifying that PydoclintTool.fix() raises NotImplementedError...",
154 )
155 tool = ToolRegistry.get("pydoclint")
156 assert_that(tool).is_not_none()
157 with pytest.raises(NotImplementedError):
158 tool.fix([str(sample_file)], {})
159 logger.info("[LOG] NotImplementedError correctly raised by PydoclintTool.fix().")
162def test_pydoclint_clean_file_passes(tmp_path: Path) -> None:
163 """Lintro PydoclintTool: Should pass on a clean file.
165 Args:
166 tmp_path: Pytest temporary directory fixture.
167 """
168 _ensure_pydoclint_available()
169 clean_file = tmp_path / "clean_module.py"
170 # Clean file following Google style with types in annotations, not docstrings
171 clean_file.write_text(
172 '''"""Clean module with proper docstrings."""
175def add_numbers(a: int, b: int) -> int:
176 """Add two numbers together.
178 Args:
179 a: The first number.
180 b: The second number.
182 Returns:
183 The sum of a and b.
184 """
185 return a + b
186''',
187 )
188 logger.info("[TEST] Running PydoclintTool on a clean file...")
189 tool = ToolRegistry.get("pydoclint")
190 assert_that(tool).is_not_none()
191 result = tool.check([str(clean_file)], {})
192 logger.info(f"[LOG] Result: success={result.success}, issues={result.issues_count}")
193 assert_that(result.success).is_true().described_as(
194 "Lintro PydoclintTool should pass on a clean file.",
195 )
196 assert_that(result.issues_count).is_equal_to(0).described_as(
197 "Lintro PydoclintTool should find no issues in a clean file.",
198 )