codeowners-coverage 0.2.1__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.2.1 → codeowners_coverage-0.3.1}/PKG-INFO +11 -4
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/README.md +8 -2
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/pyproject.toml +4 -2
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/__init__.py +1 -1
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/cli.py +52 -25
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/ollama_matcher.py +10 -2
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/PKG-INFO +11 -4
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/SOURCES.txt +1 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/requires.txt +3 -1
- codeowners_coverage-0.3.1/tests/test_cli.py +228 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_ollama_matcher.py +8 -4
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/setup.cfg +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/__main__.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/checker.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/config.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/directory_consolidator.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/git_analyzer.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/github_client.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/matcher.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggest_cache.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggester.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/dependency_links.txt +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/entry_points.txt +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/top_level.txt +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_checker.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_config.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_directory_consolidator.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_git_analyzer.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_github_client.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_matcher.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_suggest_cache.py +0 -0
- {codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/tests/test_suggester.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeowners-coverage
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Measure and enforce CODEOWNERS coverage
|
|
5
5
|
Author-email: Sentry Team <hello@sentry.io>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -10,7 +10,8 @@ Requires-Dist: pathspec>=0.12.0
|
|
|
10
10
|
Requires-Dist: PyYAML>=6.0
|
|
11
11
|
Requires-Dist: click>=8.0
|
|
12
12
|
Requires-Dist: requests>=2.31.0
|
|
13
|
-
|
|
13
|
+
Provides-Extra: suggest
|
|
14
|
+
Requires-Dist: ollama>=0.3.0; extra == "suggest"
|
|
14
15
|
Provides-Extra: dev
|
|
15
16
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
17
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -57,7 +58,11 @@ just ollama-setup # Downloads and configures Ollama model
|
|
|
57
58
|
## Installation
|
|
58
59
|
|
|
59
60
|
```bash
|
|
61
|
+
# Core (check + baseline commands)
|
|
60
62
|
pip install codeowners-coverage
|
|
63
|
+
|
|
64
|
+
# With AI-powered suggestions (requires Ollama)
|
|
65
|
+
pip install codeowners-coverage[suggest]
|
|
61
66
|
```
|
|
62
67
|
|
|
63
68
|
## Usage
|
|
@@ -76,6 +81,8 @@ codeowners-coverage baseline
|
|
|
76
81
|
|
|
77
82
|
### Suggest CODEOWNERS entries (AI-powered)
|
|
78
83
|
|
|
84
|
+
> Requires the `[suggest]` extra: `pip install codeowners-coverage[suggest]`
|
|
85
|
+
|
|
79
86
|
Use local LLM to intelligently suggest team ownership based on git history.
|
|
80
87
|
|
|
81
88
|
**GitHub Token Setup:**
|
|
@@ -251,8 +258,8 @@ just release
|
|
|
251
258
|
### Manual commands
|
|
252
259
|
|
|
253
260
|
```bash
|
|
254
|
-
# Install in development mode
|
|
255
|
-
uv pip install -e ".[dev]"
|
|
261
|
+
# Install in development mode (all features)
|
|
262
|
+
uv pip install -e ".[dev,suggest]"
|
|
256
263
|
|
|
257
264
|
# Run tests
|
|
258
265
|
pytest tests/ -v
|
|
@@ -36,7 +36,11 @@ just ollama-setup # Downloads and configures Ollama model
|
|
|
36
36
|
## Installation
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
+
# Core (check + baseline commands)
|
|
39
40
|
pip install codeowners-coverage
|
|
41
|
+
|
|
42
|
+
# With AI-powered suggestions (requires Ollama)
|
|
43
|
+
pip install codeowners-coverage[suggest]
|
|
40
44
|
```
|
|
41
45
|
|
|
42
46
|
## Usage
|
|
@@ -55,6 +59,8 @@ codeowners-coverage baseline
|
|
|
55
59
|
|
|
56
60
|
### Suggest CODEOWNERS entries (AI-powered)
|
|
57
61
|
|
|
62
|
+
> Requires the `[suggest]` extra: `pip install codeowners-coverage[suggest]`
|
|
63
|
+
|
|
58
64
|
Use local LLM to intelligently suggest team ownership based on git history.
|
|
59
65
|
|
|
60
66
|
**GitHub Token Setup:**
|
|
@@ -230,8 +236,8 @@ just release
|
|
|
230
236
|
### Manual commands
|
|
231
237
|
|
|
232
238
|
```bash
|
|
233
|
-
# Install in development mode
|
|
234
|
-
uv pip install -e ".[dev]"
|
|
239
|
+
# Install in development mode (all features)
|
|
240
|
+
uv pip install -e ".[dev,suggest]"
|
|
235
241
|
|
|
236
242
|
# Run tests
|
|
237
243
|
pytest tests/ -v
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeowners-coverage"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "Measure and enforce CODEOWNERS coverage"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -17,13 +17,15 @@ dependencies = [
|
|
|
17
17
|
"PyYAML>=6.0", # Config file parsing
|
|
18
18
|
"click>=8.0", # CLI framework
|
|
19
19
|
"requests>=2.31.0", # For GitHub API
|
|
20
|
-
"ollama>=0.3.0", # For local LLM integration
|
|
21
20
|
]
|
|
22
21
|
|
|
23
22
|
[project.scripts]
|
|
24
23
|
codeowners-coverage = "codeowners_coverage.__main__:main"
|
|
25
24
|
|
|
26
25
|
[project.optional-dependencies]
|
|
26
|
+
suggest = [
|
|
27
|
+
"ollama>=0.3.0", # For local LLM integration (suggest command)
|
|
28
|
+
]
|
|
27
29
|
dev = [
|
|
28
30
|
"pytest>=7.0",
|
|
29
31
|
"pytest-cov>=4.0",
|
|
@@ -6,21 +6,18 @@ import json
|
|
|
6
6
|
import sys
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any, Dict, List, Tuple, cast
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, cast
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
13
|
-
from .checker import CoverageChecker
|
|
14
|
-
from .config import Config
|
|
15
|
-
from .directory_consolidator import DirectoryConsolidator
|
|
16
|
-
from .git_analyzer import GitHistoryAnalyzer
|
|
17
|
-
from .github_client import GitHubClient
|
|
18
|
-
from .matcher import CodeOwnersPatternMatcher
|
|
19
|
-
from .ollama_matcher import OllamaLLMMatcher, TeamSuggestion
|
|
20
|
-
from .suggest_cache import SuggestCache
|
|
21
|
-
from .suggester import OwnershipSuggester, SuggestionResult
|
|
22
13
|
from . import __version__
|
|
23
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .config import Config
|
|
17
|
+
from .ollama_matcher import TeamSuggestion
|
|
18
|
+
from .suggest_cache import SuggestCache
|
|
19
|
+
from .suggester import SuggestionResult
|
|
20
|
+
|
|
24
21
|
|
|
25
22
|
@click.group()
|
|
26
23
|
@click.version_option(version=__version__)
|
|
@@ -33,7 +30,12 @@ def cli() -> None:
|
|
|
33
30
|
@click.option("--json", "output_json", is_flag=True, help="Output JSON format")
|
|
34
31
|
@click.option("--files", multiple=True, help="Specific files to check")
|
|
35
32
|
@click.option("--config", default=".codeowners-config.yml", help="Config file path")
|
|
36
|
-
|
|
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:
|
|
37
39
|
"""
|
|
38
40
|
Check CODEOWNERS coverage.
|
|
39
41
|
|
|
@@ -42,30 +44,30 @@ def check(output_json: bool, files: Tuple[str, ...], config: str) -> None:
|
|
|
42
44
|
New uncovered files will cause the check to fail.
|
|
43
45
|
"""
|
|
44
46
|
try:
|
|
47
|
+
from .checker import CoverageChecker
|
|
48
|
+
from .config import Config
|
|
49
|
+
|
|
45
50
|
cfg = Config.load(config)
|
|
46
51
|
checker = CoverageChecker(cfg)
|
|
47
52
|
|
|
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:
|
|
@@ -95,6 +105,9 @@ def baseline(config: str, files: Tuple[str, ...]) -> None:
|
|
|
95
105
|
allowing existing gaps.
|
|
96
106
|
"""
|
|
97
107
|
try:
|
|
108
|
+
from .checker import CoverageChecker
|
|
109
|
+
from .config import Config
|
|
110
|
+
|
|
98
111
|
cfg = Config.load(config)
|
|
99
112
|
checker = CoverageChecker(cfg)
|
|
100
113
|
|
|
@@ -198,6 +211,15 @@ def suggest(
|
|
|
198
211
|
Requires Ollama to be installed and running.
|
|
199
212
|
"""
|
|
200
213
|
try:
|
|
214
|
+
from .checker import CoverageChecker
|
|
215
|
+
from .config import Config
|
|
216
|
+
from .directory_consolidator import DirectoryConsolidator
|
|
217
|
+
from .git_analyzer import GitHistoryAnalyzer
|
|
218
|
+
from .github_client import GitHubClient
|
|
219
|
+
from .matcher import CodeOwnersPatternMatcher
|
|
220
|
+
from .ollama_matcher import OllamaLLMMatcher
|
|
221
|
+
from .suggester import OwnershipSuggester
|
|
222
|
+
|
|
201
223
|
# Load config
|
|
202
224
|
cfg = Config.load(config)
|
|
203
225
|
|
|
@@ -308,6 +330,9 @@ def suggest(
|
|
|
308
330
|
f"✓ Connected to Ollama "
|
|
309
331
|
f"(model: {cfg.ollama_model})"
|
|
310
332
|
)
|
|
333
|
+
except ImportError as e:
|
|
334
|
+
click.echo(f"❌ {e}", err=True)
|
|
335
|
+
sys.exit(1)
|
|
311
336
|
except Exception as e:
|
|
312
337
|
click.echo(
|
|
313
338
|
f"❌ Failed to connect to Ollama: {e}",
|
|
@@ -397,6 +422,8 @@ def _setup_cache(
|
|
|
397
422
|
files: List[str],
|
|
398
423
|
) -> SuggestCache | None:
|
|
399
424
|
"""Load or initialize the suggest cache."""
|
|
425
|
+
from .suggest_cache import SuggestCache
|
|
426
|
+
|
|
400
427
|
if no_cache:
|
|
401
428
|
click.echo(" Cache: disabled (--no-cache)")
|
|
402
429
|
return None
|
{codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/ollama_matcher.py
RENAMED
|
@@ -6,7 +6,10 @@ import json
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import Dict, List, Tuple
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
try:
|
|
10
|
+
import ollama
|
|
11
|
+
except ImportError:
|
|
12
|
+
ollama = None # type: ignore[assignment]
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
@dataclass
|
|
@@ -38,10 +41,15 @@ class OllamaLLMMatcher:
|
|
|
38
41
|
Raises:
|
|
39
42
|
Exception: If unable to connect to Ollama
|
|
40
43
|
"""
|
|
44
|
+
if ollama is None:
|
|
45
|
+
raise ImportError(
|
|
46
|
+
"The 'ollama' package is required for the suggest command. "
|
|
47
|
+
"Install it with: pip install codeowners-coverage[suggest]"
|
|
48
|
+
)
|
|
49
|
+
|
|
41
50
|
self.model = model
|
|
42
51
|
self.base_url = base_url
|
|
43
52
|
|
|
44
|
-
# Verify Ollama is available
|
|
45
53
|
try:
|
|
46
54
|
ollama.list()
|
|
47
55
|
except Exception as e:
|
{codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeowners-coverage
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Measure and enforce CODEOWNERS coverage
|
|
5
5
|
Author-email: Sentry Team <hello@sentry.io>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -10,7 +10,8 @@ Requires-Dist: pathspec>=0.12.0
|
|
|
10
10
|
Requires-Dist: PyYAML>=6.0
|
|
11
11
|
Requires-Dist: click>=8.0
|
|
12
12
|
Requires-Dist: requests>=2.31.0
|
|
13
|
-
|
|
13
|
+
Provides-Extra: suggest
|
|
14
|
+
Requires-Dist: ollama>=0.3.0; extra == "suggest"
|
|
14
15
|
Provides-Extra: dev
|
|
15
16
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
16
17
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -57,7 +58,11 @@ just ollama-setup # Downloads and configures Ollama model
|
|
|
57
58
|
## Installation
|
|
58
59
|
|
|
59
60
|
```bash
|
|
61
|
+
# Core (check + baseline commands)
|
|
60
62
|
pip install codeowners-coverage
|
|
63
|
+
|
|
64
|
+
# With AI-powered suggestions (requires Ollama)
|
|
65
|
+
pip install codeowners-coverage[suggest]
|
|
61
66
|
```
|
|
62
67
|
|
|
63
68
|
## Usage
|
|
@@ -76,6 +81,8 @@ codeowners-coverage baseline
|
|
|
76
81
|
|
|
77
82
|
### Suggest CODEOWNERS entries (AI-powered)
|
|
78
83
|
|
|
84
|
+
> Requires the `[suggest]` extra: `pip install codeowners-coverage[suggest]`
|
|
85
|
+
|
|
79
86
|
Use local LLM to intelligently suggest team ownership based on git history.
|
|
80
87
|
|
|
81
88
|
**GitHub Token Setup:**
|
|
@@ -251,8 +258,8 @@ just release
|
|
|
251
258
|
### Manual commands
|
|
252
259
|
|
|
253
260
|
```bash
|
|
254
|
-
# Install in development mode
|
|
255
|
-
uv pip install -e ".[dev]"
|
|
261
|
+
# Install in development mode (all features)
|
|
262
|
+
uv pip install -e ".[dev,suggest]"
|
|
256
263
|
|
|
257
264
|
# Run tests
|
|
258
265
|
pytest tests/ -v
|
{codeowners_coverage-0.2.1 → 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
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from unittest.mock import patch
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
+
from codeowners_coverage import ollama_matcher
|
|
9
10
|
from codeowners_coverage.ollama_matcher import OllamaLLMMatcher
|
|
10
11
|
|
|
12
|
+
_mock_ollama_module = MagicMock()
|
|
13
|
+
_mock_ollama_module.list.return_value = []
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
@pytest.fixture
|
|
13
17
|
def mock_ollama() -> None:
|
|
14
18
|
"""Mock Ollama availability check."""
|
|
15
|
-
with patch("ollama
|
|
19
|
+
with patch.object(ollama_matcher, "ollama", _mock_ollama_module):
|
|
16
20
|
yield
|
|
17
21
|
|
|
18
22
|
|
|
@@ -91,7 +95,7 @@ def test_match_file_to_team(mock_ollama: None) -> None:
|
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
with patch("
|
|
98
|
+
with patch.object(_mock_ollama_module, "chat", return_value=mock_response):
|
|
95
99
|
suggestion = matcher.match_file_to_team(
|
|
96
100
|
filepath="src/components/Button.tsx",
|
|
97
101
|
contributors=[("alice@example.com", 5)],
|
|
@@ -292,7 +296,7 @@ def test_match_file_passes_allowlist(mock_ollama: None) -> None:
|
|
|
292
296
|
}
|
|
293
297
|
}
|
|
294
298
|
|
|
295
|
-
with patch("
|
|
299
|
+
with patch.object(_mock_ollama_module, "chat", return_value=mock_response):
|
|
296
300
|
suggestion = matcher.match_file_to_team(
|
|
297
301
|
filepath="src/components/Button.tsx",
|
|
298
302
|
contributors=[("alice@example.com", 5)],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/git_analyzer.py
RENAMED
|
File without changes
|
{codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/github_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{codeowners_coverage-0.2.1 → codeowners_coverage-0.3.1}/src/codeowners_coverage/suggest_cache.py
RENAMED
|
File without changes
|
{codeowners_coverage-0.2.1 → 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
|
{codeowners_coverage-0.2.1 → 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
|