codeowners-coverage 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/PKG-INFO +1 -1
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/pyproject.toml +1 -1
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/__init__.py +1 -1
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/cli.py +27 -18
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/PKG-INFO +1 -1
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/SOURCES.txt +1 -0
- codeowners_coverage-0.3.1/tests/test_cli.py +228 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/README.md +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/setup.cfg +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/__main__.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/checker.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/config.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/directory_consolidator.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/git_analyzer.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/github_client.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/matcher.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/ollama_matcher.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggest_cache.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggester.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/dependency_links.txt +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/entry_points.txt +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/requires.txt +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/top_level.txt +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_checker.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_config.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_directory_consolidator.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_git_analyzer.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_github_client.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_matcher.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_ollama_matcher.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_suggest_cache.py +0 -0
- {codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_suggester.py +0 -0
|
@@ -30,7 +30,12 @@ def cli() -> None:
|
|
|
30
30
|
@click.option("--json", "output_json", is_flag=True, help="Output JSON format")
|
|
31
31
|
@click.option("--files", multiple=True, help="Specific files to check")
|
|
32
32
|
@click.option("--config", default=".codeowners-config.yml", help="Config file path")
|
|
33
|
-
|
|
33
|
+
@click.option(
|
|
34
|
+
"--allow-dirty-baseline",
|
|
35
|
+
is_flag=True,
|
|
36
|
+
help="Don't fail when baseline contains entries that are now covered",
|
|
37
|
+
)
|
|
38
|
+
def check(output_json: bool, files: Tuple[str, ...], config: str, allow_dirty_baseline: bool) -> None:
|
|
34
39
|
"""
|
|
35
40
|
Check CODEOWNERS coverage.
|
|
36
41
|
|
|
@@ -48,24 +53,21 @@ def check(output_json: bool, files: Tuple[str, ...], config: str) -> None:
|
|
|
48
53
|
file_list: List[str] | None = list(files) if files else None
|
|
49
54
|
result = checker.check_coverage(file_list)
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
click.echo(json.dumps(result, indent=2))
|
|
53
|
-
else:
|
|
54
|
-
_print_human_readable_result(result)
|
|
55
|
-
|
|
56
|
-
# Exit with error if there are new uncovered files
|
|
57
|
-
if result["uncovered_files"]:
|
|
58
|
-
sys.exit(1)
|
|
59
|
-
|
|
60
|
-
# Exit with code 2 if baseline can be reduced (positive signal)
|
|
61
|
-
# Check if any baseline entries (literal paths or glob patterns)
|
|
62
|
-
# no longer match any uncovered files
|
|
56
|
+
# Calculate unused baseline entries early
|
|
63
57
|
loaded_baseline = checker._load_baseline()
|
|
64
58
|
baseline_matched = cast(List[str], result["baseline_files"])
|
|
65
59
|
unused_entries = loaded_baseline.get_unused_entries(baseline_matched)
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
# Output results
|
|
62
|
+
if output_json:
|
|
63
|
+
json_output = result.copy()
|
|
64
|
+
json_output["unused_baseline_entries"] = unused_entries
|
|
65
|
+
click.echo(json.dumps(json_output, indent=2))
|
|
66
|
+
else:
|
|
67
|
+
_print_human_readable_result(result)
|
|
68
|
+
|
|
69
|
+
# Print unused entries message in human-readable mode
|
|
70
|
+
if unused_entries:
|
|
69
71
|
click.echo(f"\n🎉 Great news! {len(unused_entries)} baseline entries can be removed:")
|
|
70
72
|
for entry in unused_entries[:10]:
|
|
71
73
|
click.echo(f" - {entry}")
|
|
@@ -73,6 +75,14 @@ def check(output_json: bool, files: Tuple[str, ...], config: str) -> None:
|
|
|
73
75
|
click.echo(f" ... and {len(unused_entries) - 10} more")
|
|
74
76
|
click.echo("\nThese entries now have CODEOWNERS coverage! Update the baseline:")
|
|
75
77
|
click.echo(" codeowners-coverage baseline")
|
|
78
|
+
|
|
79
|
+
# Exit with error if there are new uncovered files
|
|
80
|
+
if result["uncovered_files"]:
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
# Exit with code 2 if baseline can be reduced (positive signal)
|
|
84
|
+
# Only exit with code 2 if baseline is dirty AND flag is not set
|
|
85
|
+
if unused_entries and not allow_dirty_baseline:
|
|
76
86
|
sys.exit(2)
|
|
77
87
|
|
|
78
88
|
except FileNotFoundError as e:
|
|
@@ -207,9 +217,8 @@ def suggest(
|
|
|
207
217
|
from .git_analyzer import GitHistoryAnalyzer
|
|
208
218
|
from .github_client import GitHubClient
|
|
209
219
|
from .matcher import CodeOwnersPatternMatcher
|
|
210
|
-
from .ollama_matcher import OllamaLLMMatcher
|
|
211
|
-
from .
|
|
212
|
-
from .suggester import OwnershipSuggester, SuggestionResult
|
|
220
|
+
from .ollama_matcher import OllamaLLMMatcher
|
|
221
|
+
from .suggester import OwnershipSuggester
|
|
213
222
|
|
|
214
223
|
# Load config
|
|
215
224
|
cfg = Config.load(config)
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/SOURCES.txt
RENAMED
|
@@ -19,6 +19,7 @@ src/codeowners_coverage.egg-info/entry_points.txt
|
|
|
19
19
|
src/codeowners_coverage.egg-info/requires.txt
|
|
20
20
|
src/codeowners_coverage.egg-info/top_level.txt
|
|
21
21
|
tests/test_checker.py
|
|
22
|
+
tests/test_cli.py
|
|
22
23
|
tests/test_config.py
|
|
23
24
|
tests/test_directory_consolidator.py
|
|
24
25
|
tests/test_git_analyzer.py
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Tests for CLI functionality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
|
|
10
|
+
from click.testing import CliRunner
|
|
11
|
+
|
|
12
|
+
from codeowners_coverage.cli import cli
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_check_dirty_baseline_without_flag_exits_2() -> None:
|
|
16
|
+
"""Test that check exits with code 2 when baseline is dirty (default behavior)."""
|
|
17
|
+
runner = CliRunner()
|
|
18
|
+
|
|
19
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
20
|
+
# Create CODEOWNERS file
|
|
21
|
+
codeowners_path = Path(tmpdir) / "CODEOWNERS"
|
|
22
|
+
codeowners_path.write_text("*.py @python-team\n")
|
|
23
|
+
|
|
24
|
+
# Create baseline with an entry that now has coverage
|
|
25
|
+
baseline_path = Path(tmpdir) / "baseline.txt"
|
|
26
|
+
baseline_path.write_text("covered.py\n")
|
|
27
|
+
|
|
28
|
+
# Create config
|
|
29
|
+
config_path = Path(tmpdir) / ".codeowners-config.yml"
|
|
30
|
+
config_path.write_text(
|
|
31
|
+
f"codeowners_path: {codeowners_path}\n"
|
|
32
|
+
f"baseline_path: {baseline_path}\n"
|
|
33
|
+
f"exclusions: []\n"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Mock get_repository_files to return empty (all files covered)
|
|
37
|
+
with patch(
|
|
38
|
+
"codeowners_coverage.checker.CoverageChecker.get_repository_files",
|
|
39
|
+
return_value=["covered.py"],
|
|
40
|
+
):
|
|
41
|
+
result = runner.invoke(cli, ["check", "--config", str(config_path)])
|
|
42
|
+
|
|
43
|
+
# Should exit with code 2 (baseline can be reduced)
|
|
44
|
+
assert result.exit_code == 2
|
|
45
|
+
assert "baseline entries can be removed" in result.output
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_check_dirty_baseline_with_flag_exits_0() -> None:
|
|
49
|
+
"""Test that check exits with code 0 when baseline is dirty but flag is set."""
|
|
50
|
+
runner = CliRunner()
|
|
51
|
+
|
|
52
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
53
|
+
# Create CODEOWNERS file
|
|
54
|
+
codeowners_path = Path(tmpdir) / "CODEOWNERS"
|
|
55
|
+
codeowners_path.write_text("*.py @python-team\n")
|
|
56
|
+
|
|
57
|
+
# Create baseline with an entry that now has coverage
|
|
58
|
+
baseline_path = Path(tmpdir) / "baseline.txt"
|
|
59
|
+
baseline_path.write_text("covered.py\n")
|
|
60
|
+
|
|
61
|
+
# Create config
|
|
62
|
+
config_path = Path(tmpdir) / ".codeowners-config.yml"
|
|
63
|
+
config_path.write_text(
|
|
64
|
+
f"codeowners_path: {codeowners_path}\n"
|
|
65
|
+
f"baseline_path: {baseline_path}\n"
|
|
66
|
+
f"exclusions: []\n"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Mock get_repository_files to return covered files
|
|
70
|
+
with patch(
|
|
71
|
+
"codeowners_coverage.checker.CoverageChecker.get_repository_files",
|
|
72
|
+
return_value=["covered.py"],
|
|
73
|
+
):
|
|
74
|
+
result = runner.invoke(
|
|
75
|
+
cli, ["check", "--config", str(config_path), "--allow-dirty-baseline"]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Should exit with code 0 (success despite dirty baseline)
|
|
79
|
+
assert result.exit_code == 0
|
|
80
|
+
assert "baseline entries can be removed" in result.output
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_check_clean_baseline_with_flag_exits_0() -> None:
|
|
84
|
+
"""Test that check exits with code 0 when baseline is clean (with or without flag)."""
|
|
85
|
+
runner = CliRunner()
|
|
86
|
+
|
|
87
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
88
|
+
# Create CODEOWNERS file
|
|
89
|
+
codeowners_path = Path(tmpdir) / "CODEOWNERS"
|
|
90
|
+
codeowners_path.write_text("*.py @python-team\n")
|
|
91
|
+
|
|
92
|
+
# Create empty baseline
|
|
93
|
+
baseline_path = Path(tmpdir) / "baseline.txt"
|
|
94
|
+
baseline_path.write_text("")
|
|
95
|
+
|
|
96
|
+
# Create config
|
|
97
|
+
config_path = Path(tmpdir) / ".codeowners-config.yml"
|
|
98
|
+
config_path.write_text(
|
|
99
|
+
f"codeowners_path: {codeowners_path}\n"
|
|
100
|
+
f"baseline_path: {baseline_path}\n"
|
|
101
|
+
f"exclusions: []\n"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Mock get_repository_files to return covered files
|
|
105
|
+
with patch(
|
|
106
|
+
"codeowners_coverage.checker.CoverageChecker.get_repository_files",
|
|
107
|
+
return_value=["covered.py"],
|
|
108
|
+
):
|
|
109
|
+
result = runner.invoke(
|
|
110
|
+
cli, ["check", "--config", str(config_path), "--allow-dirty-baseline"]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Should exit with code 0
|
|
114
|
+
assert result.exit_code == 0
|
|
115
|
+
assert "baseline entries can be removed" not in result.output
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_check_json_output_includes_unused_baseline_entries() -> None:
|
|
119
|
+
"""Test that JSON output includes unused_baseline_entries field."""
|
|
120
|
+
runner = CliRunner()
|
|
121
|
+
|
|
122
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
123
|
+
# Create CODEOWNERS file
|
|
124
|
+
codeowners_path = Path(tmpdir) / "CODEOWNERS"
|
|
125
|
+
codeowners_path.write_text("*.py @python-team\n")
|
|
126
|
+
|
|
127
|
+
# Create baseline with entries that now have coverage
|
|
128
|
+
baseline_path = Path(tmpdir) / "baseline.txt"
|
|
129
|
+
baseline_path.write_text("covered1.py\ncovered2.py\n")
|
|
130
|
+
|
|
131
|
+
# Create config
|
|
132
|
+
config_path = Path(tmpdir) / ".codeowners-config.yml"
|
|
133
|
+
config_path.write_text(
|
|
134
|
+
f"codeowners_path: {codeowners_path}\n"
|
|
135
|
+
f"baseline_path: {baseline_path}\n"
|
|
136
|
+
f"exclusions: []\n"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Mock get_repository_files to return covered files
|
|
140
|
+
with patch(
|
|
141
|
+
"codeowners_coverage.checker.CoverageChecker.get_repository_files",
|
|
142
|
+
return_value=["covered1.py", "covered2.py"],
|
|
143
|
+
):
|
|
144
|
+
result = runner.invoke(
|
|
145
|
+
cli, ["check", "--config", str(config_path), "--json"]
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Parse JSON output
|
|
149
|
+
output = json.loads(result.output)
|
|
150
|
+
|
|
151
|
+
# Should include unused_baseline_entries
|
|
152
|
+
assert "unused_baseline_entries" in output
|
|
153
|
+
assert "covered1.py" in output["unused_baseline_entries"]
|
|
154
|
+
assert "covered2.py" in output["unused_baseline_entries"]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_check_new_uncovered_takes_precedence_over_dirty_baseline() -> None:
|
|
158
|
+
"""Test that new uncovered files cause exit 1 even with --allow-dirty-baseline."""
|
|
159
|
+
runner = CliRunner()
|
|
160
|
+
|
|
161
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
162
|
+
# Create CODEOWNERS file
|
|
163
|
+
codeowners_path = Path(tmpdir) / "CODEOWNERS"
|
|
164
|
+
codeowners_path.write_text("*.py @python-team\n")
|
|
165
|
+
|
|
166
|
+
# Create baseline with an entry that now has coverage
|
|
167
|
+
baseline_path = Path(tmpdir) / "baseline.txt"
|
|
168
|
+
baseline_path.write_text("covered.py\n")
|
|
169
|
+
|
|
170
|
+
# Create config
|
|
171
|
+
config_path = Path(tmpdir) / ".codeowners-config.yml"
|
|
172
|
+
config_path.write_text(
|
|
173
|
+
f"codeowners_path: {codeowners_path}\n"
|
|
174
|
+
f"baseline_path: {baseline_path}\n"
|
|
175
|
+
f"exclusions: []\n"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Mock get_repository_files to return covered + new uncovered file
|
|
179
|
+
with patch(
|
|
180
|
+
"codeowners_coverage.checker.CoverageChecker.get_repository_files",
|
|
181
|
+
return_value=["covered.py", "new_uncovered.txt"],
|
|
182
|
+
):
|
|
183
|
+
result = runner.invoke(
|
|
184
|
+
cli, ["check", "--config", str(config_path), "--allow-dirty-baseline"]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Should exit with code 1 (new uncovered files)
|
|
188
|
+
assert result.exit_code == 1
|
|
189
|
+
assert "new_uncovered.txt" in result.output
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_check_json_output_with_clean_baseline() -> None:
|
|
193
|
+
"""Test that JSON output includes empty unused_baseline_entries when baseline is clean."""
|
|
194
|
+
runner = CliRunner()
|
|
195
|
+
|
|
196
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
197
|
+
# Create CODEOWNERS file
|
|
198
|
+
codeowners_path = Path(tmpdir) / "CODEOWNERS"
|
|
199
|
+
codeowners_path.write_text("*.py @python-team\n")
|
|
200
|
+
|
|
201
|
+
# Create baseline with entries still uncovered
|
|
202
|
+
baseline_path = Path(tmpdir) / "baseline.txt"
|
|
203
|
+
baseline_path.write_text("still_uncovered.txt\n")
|
|
204
|
+
|
|
205
|
+
# Create config
|
|
206
|
+
config_path = Path(tmpdir) / ".codeowners-config.yml"
|
|
207
|
+
config_path.write_text(
|
|
208
|
+
f"codeowners_path: {codeowners_path}\n"
|
|
209
|
+
f"baseline_path: {baseline_path}\n"
|
|
210
|
+
f"exclusions: []\n"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Mock get_repository_files to return baseline file (still uncovered)
|
|
214
|
+
with patch(
|
|
215
|
+
"codeowners_coverage.checker.CoverageChecker.get_repository_files",
|
|
216
|
+
return_value=["covered.py", "still_uncovered.txt"],
|
|
217
|
+
):
|
|
218
|
+
result = runner.invoke(
|
|
219
|
+
cli, ["check", "--config", str(config_path), "--json"]
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# Parse JSON output
|
|
223
|
+
output = json.loads(result.output)
|
|
224
|
+
|
|
225
|
+
# Should include empty unused_baseline_entries
|
|
226
|
+
assert "unused_baseline_entries" in output
|
|
227
|
+
assert output["unused_baseline_entries"] == []
|
|
228
|
+
assert result.exit_code == 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/git_analyzer.py
RENAMED
|
File without changes
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/github_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/ollama_matcher.py
RENAMED
|
File without changes
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggest_cache.py
RENAMED
|
File without changes
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggester.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeowners_coverage-0.3.0 → codeowners_coverage-0.3.1}/tests/test_directory_consolidator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|