diff --git a/README.md b/README.md index 3c7bae5..374f691 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ 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: @@ -12,7 +12,9 @@ also recommended: `pip install coverage` ### Usage + Typical execution: + ```bash coverage run -m pytest tests/ # or any test runner here coverage json @@ -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 diff --git a/coverage_threshold/cli/read_config.py b/coverage_threshold/cli/read_config.py index 24c9a53..b1803f6 100644 --- a/coverage_threshold/cli/read_config.py +++ b/coverage_threshold/cli/read_config.py @@ -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() diff --git a/example_project/pyproject.toml b/example_project/pyproject.toml index b59c6db..9bb6a5c 100644 --- a/example_project/pyproject.toml +++ b/example_project/pyproject.toml @@ -1,2 +1,2 @@ -[coverage-threshold] +[tool.coverage-threshold] line_coverage_min = 75.0 diff --git a/pyproject.toml b/pyproject.toml index d864e1f..1c17ff3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 diff --git a/tests/unit/config/__init__.py b/tests/unit/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/config/complex.pyproject.toml b/tests/unit/config/complex.pyproject.toml new file mode 100644 index 0000000..7d1f290 --- /dev/null +++ b/tests/unit/config/complex.pyproject.toml @@ -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 diff --git a/tests/unit/config/legacy.pyproject.toml b/tests/unit/config/legacy.pyproject.toml new file mode 100644 index 0000000..b59c6db --- /dev/null +++ b/tests/unit/config/legacy.pyproject.toml @@ -0,0 +1,2 @@ +[coverage-threshold] +line_coverage_min = 75.0 diff --git a/tests/unit/config/pyproject.toml b/tests/unit/config/pyproject.toml new file mode 100644 index 0000000..9bb6a5c --- /dev/null +++ b/tests/unit/config/pyproject.toml @@ -0,0 +1,2 @@ +[tool.coverage-threshold] +line_coverage_min = 75.0 diff --git a/tests/unit/config/test_read_config.py b/tests/unit/config/test_read_config.py new file mode 100644 index 0000000..1985fea --- /dev/null +++ b/tests/unit/config/test_read_config.py @@ -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)