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
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
A command line tool for checking coverage reports against configurable coverage minimums.
Currently built for use around python's [coverage](https://pypi.org/project/coverage/)


### Installation

`pip install coverage-threshold`

also recommended:

`pip install coverage`

### Usage

Typical execution:

```bash
coverage run -m pytest tests/ # or any test runner here
coverage json
Expand Down Expand Up @@ -52,34 +54,34 @@ optional arguments:
--config CONFIG path to config file (default: ./pyproject.toml)
```


### Config

the current expected config file format is [toml](https://toml.io/en/)
the default config file used is `pyproject.toml` but and alternative path can be specified with `--config`

example config:

```toml
[coverage-threshold]
[tool.coverage-threshold]
line_coverage_min = 95
file_line_coverage_min = 95
branch_coverage_min = 50

[coverage-threshold.modules."src/cli/"]
file_line_coverage_min = 40
[tool.coverage-threshold.modules."src/cli/"]
file_line_coverage_min = 40

[coverage-threshold.modules."src/cli/my_command.py"]
file_line_coverage_min = 100
[tool.coverage-threshold.modules."src/cli/my_command.py"]
file_line_coverage_min = 100

[coverage-threshold.modules."src/lib/"]
file_line_coverage_min = 100
file_branch_coverage_min = 100
[tool.coverage-threshold.modules."src/lib/"]
file_line_coverage_min = 100
file_branch_coverage_min = 100

[coverage-threshold.modules."src/model/"]
file_line_coverage_min = 100
[tool.coverage-threshold.modules."src/model/"]
file_line_coverage_min = 100

[coverage-threshold.modules."src/__main__.py"]
file_line_coverage_min = 0
[tool.coverage-threshold.modules."src/__main__.py"]
file_line_coverage_min = 0
```

Each string key in `config.modules` is treated as a path prefix, where the longest matching prefix is used to configure the coverage thresholds for each file
21 changes: 14 additions & 7 deletions coverage_threshold/cli/read_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
def read_config(config_file_name: Optional[str]) -> Config:
DEFAULT_FILENAME = "./pyproject.toml"
if config_file_name is not None:
return Config.parse(toml.load(config_file_name)["coverage-threshold"])
if not os.path.isfile(config_file_name):
raise FileNotFoundError(f"Config file {config_file_name} not found")
else:
if os.path.isfile(DEFAULT_FILENAME):
return Config.parse(
toml.load(DEFAULT_FILENAME).get("coverage-threshold", {})
)
else:
return Config()
config_file_name = DEFAULT_FILENAME
if os.path.isfile(config_file_name):
toml_dict = toml.load(config_file_name)
try:
# PEP 518 compliant version
config_dict = toml_dict["tool"]["coverage-threshold"]
except KeyError:
# Legacy version
config_dict = toml_dict.get("coverage-threshold", {})
return Config.parse(config_dict)
else:
return Config()
2 changes: 1 addition & 1 deletion example_project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[coverage-threshold]
[tool.coverage-threshold]
line_coverage_min = 75.0
14 changes: 7 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ build-backend = "setuptools.build_meta"
profile = "black"
src_paths = ["coverage_threshold", "tests"]

[coverage-threshold]
[tool.coverage-threshold]
line_coverage_min = 0
file_line_coverage_min = 100
file_branch_coverage_min = 100

[coverage-threshold.modules."coverage_threshold/cli/"]
file_line_coverage_min = 0
file_branch_coverage_min = 0
[tool.coverage-threshold.modules."coverage_threshold/cli/"]
file_line_coverage_min = 0
file_branch_coverage_min = 0

[coverage-threshold.modules."coverage_threshold/__main__.py"]
file_line_coverage_min = 0
file_branch_coverage_min = 0
[tool.coverage-threshold.modules."coverage_threshold/__main__.py"]
file_line_coverage_min = 0
file_branch_coverage_min = 0
Empty file added tests/unit/config/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions tests/unit/config/complex.pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[tool.coverage-threshold]
line_coverage_min = 95
file_line_coverage_min = 95
branch_coverage_min = 50

[tool.coverage-threshold.modules."src/cli/"]
file_line_coverage_min = 40

[tool.coverage-threshold.modules."src/cli/my_command.py"]
file_line_coverage_min = 100

[tool.coverage-threshold.modules."src/lib/"]
file_line_coverage_min = 100
file_branch_coverage_min = 100

[tool.coverage-threshold.modules."src/model/"]
file_line_coverage_min = 100

[tool.coverage-threshold.modules."src/__main__.py"]
file_line_coverage_min = 0
2 changes: 2 additions & 0 deletions tests/unit/config/legacy.pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[coverage-threshold]
line_coverage_min = 75.0
2 changes: 2 additions & 0 deletions tests/unit/config/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.coverage-threshold]
line_coverage_min = 75.0
37 changes: 37 additions & 0 deletions tests/unit/config/test_read_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os
from decimal import Decimal

from coverage_threshold.cli.read_config import read_config


def test_read_config_parses_default_pyproject_format() -> None:
config = read_config(path_to_test_config("pyproject.toml"))
assert config.line_coverage_min == Decimal("75.0")
assert config.modules is None


# backwards compatibilty for before this project became compliant with pep518
def test_read_config_parses_legacy_pyproject_format() -> None:
config = read_config(path_to_test_config("legacy.pyproject.toml"))
assert config.line_coverage_min == Decimal("75.0")
assert config.modules is None


def test_read_config_parses_complex_pyproject_format() -> None:
config = read_config(path_to_test_config("complex.pyproject.toml"))
assert config.line_coverage_min == Decimal("95.0")
assert config.file_line_coverage_min == Decimal("95.0")
assert config.branch_coverage_min == Decimal("50.0")
assert config.modules is not None
assert config.modules["src/cli/"].file_line_coverage_min == Decimal("40.0")
assert config.modules["src/cli/my_command.py"].file_line_coverage_min == Decimal(
"100"
)
assert config.modules["src/lib/"].file_line_coverage_min == Decimal("100")
assert config.modules["src/lib/"].file_branch_coverage_min == Decimal("100")
assert config.modules["src/model/"].file_line_coverage_min == Decimal("100")
assert config.modules["src/__main__.py"].file_line_coverage_min == Decimal("0")


def path_to_test_config(filename: str) -> str:
return os.path.join(os.path.dirname(__file__), filename)