Coverage for tests / integration / test_markdownlint_integration.py: 82%
79 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 markdownlint tool."""
3import shutil
4import subprocess
5from pathlib import Path
7import pytest
8from assertpy import assert_that
9from loguru import logger
11from lintro.parsers.markdownlint.markdownlint_issue import MarkdownlintIssue
12from lintro.plugins import ToolRegistry
14logger.remove()
15logger.add(lambda msg: print(msg, end=""), level="INFO")
16SAMPLE_FILE = "test_samples/tools/config/markdown/markdownlint_violations.md"
19def find_markdownlint_cmd() -> list[str] | None:
20 """Find markdownlint-cli2 command.
22 Returns:
23 Command list if found, None otherwise
24 """
25 if shutil.which("npx"):
26 return ["npx", "--yes", "markdownlint-cli2"]
27 if shutil.which("markdownlint-cli2"):
28 return ["markdownlint-cli2"]
29 return None
32def run_markdownlint_directly(file_path: Path) -> tuple[bool, str, int]:
33 """Run markdownlint-cli2 directly on a file and return result tuple.
35 Args:
36 file_path: Path to the file to check with markdownlint-cli2.
38 Returns:
39 tuple[bool, str, int]: Success status, output text, and issue count.
40 """
41 cmd_base = find_markdownlint_cmd()
42 if cmd_base is None:
43 pytest.skip("markdownlint-cli2 not found in PATH")
44 # Use relative path from repo root to match lintro's behavior
45 repo_root = Path(__file__).parent.parent.parent
46 # Resolve to absolute path first if it's relative
47 abs_file_path = file_path.resolve() if not file_path.is_absolute() else file_path
48 try:
49 relative_path = abs_file_path.relative_to(repo_root)
50 except ValueError:
51 # Fallback: use relative path calculation if not under repo root
52 import os
54 relative_path = Path(os.path.relpath(abs_file_path, repo_root))
55 cmd = [*cmd_base, str(relative_path)]
57 result = subprocess.run(
58 cmd,
59 capture_output=True,
60 text=True,
61 check=False,
62 cwd=repo_root,
63 )
65 # Count issues from output (non-empty lines are typically issues)
66 issues = [
67 line
68 for line in result.stdout.splitlines()
69 if line.strip() and ":" in line and "MD" in line
70 ]
71 issues_count = len(issues)
72 success = issues_count == 0 and result.returncode == 0
73 return (success, result.stdout, issues_count)
76@pytest.mark.markdownlint
77def test_markdownlint_available() -> None:
78 """Check if markdownlint-cli2 is available in PATH."""
79 cmd_base = find_markdownlint_cmd()
80 if cmd_base is None:
81 pytest.skip("markdownlint-cli2 not found in PATH")
82 try:
83 cmd = [*cmd_base, "--version"]
84 result = subprocess.run(
85 cmd,
86 capture_output=True,
87 text=True,
88 check=False,
89 timeout=10,
90 )
91 assert_that(result.returncode).is_equal_to(0)
92 except (subprocess.TimeoutExpired, FileNotFoundError):
93 pytest.skip("markdownlint-cli2 not available")
96@pytest.mark.markdownlint
97def test_markdownlint_direct_vs_lintro_parity() -> None:
98 """Compare direct markdownlint-cli2 output with lintro wrapper.
100 Runs markdownlint-cli2 directly on the sample file and compares the
101 issue count with lintro's wrapper to ensure parity.
102 """
103 sample_path = Path(SAMPLE_FILE)
104 if not sample_path.exists():
105 pytest.skip(f"Sample file {SAMPLE_FILE} not found")
107 # Run markdownlint-cli2 directly
108 direct_success, direct_output, direct_count = run_markdownlint_directly(
109 sample_path,
110 )
112 # Run via lintro
113 tool = ToolRegistry.get("markdownlint")
114 assert_that(tool).is_not_none()
115 # Clear exclude patterns to allow scanning test_samples
116 tool.exclude_patterns = []
117 lintro_result = tool.check([str(sample_path)], {})
119 # Compare issue counts (allow some variance due to parsing differences)
120 # Direct count may include lines we don't parse, so lintro count <= direct
121 assert_that(lintro_result.issues_count).is_greater_than_or_equal_to(0)
122 # If direct found issues, lintro should find <= direct count (parsing may miss some)
123 if direct_count > 0:
124 assert_that(lintro_result.issues_count).is_less_than_or_equal_to(direct_count)
125 assert_that(lintro_result.issues_count).is_greater_than(0)
127 # Both should agree on success/failure
128 # Success is True when no issues found, False when issues are found
129 assert_that(lintro_result.success).is_equal_to(direct_success)
132@pytest.mark.markdownlint
133def test_markdownlint_integration_basic() -> None:
134 """Basic integration test for markdownlint tool.
136 Verifies that the tool can discover files, run checks, and parse output.
137 """
138 sample_path = Path(SAMPLE_FILE)
139 if not sample_path.exists():
140 pytest.skip(f"Sample file {SAMPLE_FILE} not found")
142 tool = ToolRegistry.get("markdownlint")
143 assert_that(tool).is_not_none()
144 result = tool.check([str(sample_path)], {})
146 assert_that(result).is_not_none()
147 assert_that(result.name).is_equal_to("markdownlint")
148 assert_that(result.issues_count).is_greater_than_or_equal_to(0)
150 # If there are issues, verify they're properly structured
151 if result.issues:
152 issue = result.issues[0]
153 # Use isinstance check for type narrowing
154 if not isinstance(issue, MarkdownlintIssue):
155 pytest.fail("issue should be MarkdownlintIssue")
156 assert_that(issue.file).is_not_empty()
157 assert_that(issue.line).is_greater_than(0)
158 assert_that(issue.code).matches(r"^MD\d+$")
159 assert_that(issue.message).is_not_empty()