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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Add `--sort-by-uncovered-lines` to sort output, so the file with the most missing lines is at the
top. Thanks @guettli
* Fix: JSON and YAML output when using `--ignore-regex` in `show` command. Thanks @OidaTiftla
* Add `--fail-threshold` to return a non-zero exit code when the total number of uncovered lines exceeds the specified threshold

## 4.1.0 (2025-04-13)

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ $ pycobertura show --format github-annotation tests/cobertura.xml
::notice file=dummy/dummy4.py,line=1,endLine=6,title=pycobertura::not covered
```

The following shows how to return a non-zero exit code when the total number of uncovered lines exceeds the specified threshold.

```shell
$ pycobertura show --fail-threshold=123 cobertura.xml
```

If you run it in GitHub Actions/Apps, the above log generates check annotations.

![Example output of github-annotation formatted pycobertura show command](images/example_github_annotation_show.png)
Expand Down
20 changes: 15 additions & 5 deletions pycobertura/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ExitCodes:
EXCEPTION = 1
COVERAGE_WORSENED = 2
NOT_ALL_CHANGES_COVERED = 3
TOTAL_MISSES_ABOVE_THRESHOLD = 4


def get_exit_code(differ: CoberturaDiff, source):
Expand Down Expand Up @@ -121,6 +122,13 @@ def get_exit_code(differ: CoberturaDiff, source):
default=False,
help="Sort the summary so files with the most uncovered lines appear first.",
)
@click.option(
"--fail-threshold",
metavar="<threshold>",
type=click.IntRange(min=1),
help="Return a non-zero code if the total number of uncovered statements "
"exceeds the threshold.",
)
def show(
cobertura_file,
ignore_regex,
Expand All @@ -133,6 +141,7 @@ def show(
annotation_level,
annotation_title,
annotation_message,
fail_threshold,
):
"""show coverage summary of a Cobertura report"""

Expand All @@ -146,7 +155,6 @@ def show(
Reporter = reporters[format]
reporter_kwargs = {}
reporter_kwargs["sort_by_uncovered_lines"] = sort_by_uncovered_lines

reporter = Reporter(cobertura, ignore_regex, **reporter_kwargs)

if format == "csv":
Expand All @@ -166,6 +174,10 @@ def show(
isatty = True if output is None else output.isatty()
click.echo(report, file=output, nl=isatty)

if fail_threshold is not None:
if cobertura.total_misses() > fail_threshold:
raise SystemExit(ExitCodes.TOTAL_MISSES_ABOVE_THRESHOLD)


delta_reporters = {
"text": TextReporterDelta,
Expand All @@ -178,8 +190,7 @@ def show(
}


@pycobertura.command(
help="""\
@pycobertura.command(help="""\
The diff command compares and shows the changes between two Cobertura reports.

NOTE: Reporting missing lines or showing the source code with the diff command
Expand All @@ -190,8 +201,7 @@ def show(
options `--source1` and `--source2` are necessary to point to the source code
directories (or zip archives). If the source is not available at all, pass
`--no-source` but missing lines and source code will not be reported.
"""
)
""")
@click.argument("cobertura_file1")
@click.argument("cobertura_file2")
@click.option(
Expand Down
1 change: 0 additions & 1 deletion pycobertura/reporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import json
import io


env = Environment(loader=PackageLoader("pycobertura", "templates"))
env.filters["line_status"] = filters.line_status
env.filters["line_reason"] = filters.line_reason_icon
Expand Down
50 changes: 50 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import os
from pycobertura.cli import ExitCodes
import pytest
from click.testing import CliRunner

Expand All @@ -12,6 +13,55 @@ def test_exit_codes():
assert ExitCodes.EXCEPTION == 1
assert ExitCodes.COVERAGE_WORSENED == 2
assert ExitCodes.NOT_ALL_CHANGES_COVERED == 3
assert ExitCodes.TOTAL_MISSES_ABOVE_THRESHOLD == 4


@pytest.mark.parametrize("fail_threshold, exit_code", ((1000, ExitCodes.OK), (1, ExitCodes.TOTAL_MISSES_ABOVE_THRESHOLD)))
def test_show__fail_threshold__exit_status(fail_threshold, exit_code):
from pycobertura.cli import show

runner = CliRunner()
result = runner.invoke(show, [
'tests/dummy.original.xml',
f'--fail-threshold={fail_threshold}',
], catch_exceptions=False)
assert result.exit_code == exit_code


@pytest.mark.parametrize('fail_threshold', (-1, 0))
def test_show__fail_threshold__invalid_value(fail_threshold):
from pycobertura.cli import show

runner = CliRunner()
result = runner.invoke(
show,
['tests/dummy.original.xml', f'--fail-threshold={fail_threshold}'],
catch_exceptions=False,
)
assert result.output == f"""\
Usage: show [OPTIONS] COBERTURA_FILE
Try 'show --help' for help.

Error: Invalid value for '--fail-threshold': {fail_threshold} is not in the range x>=1.
"""


@pytest.mark.parametrize('fail_threshold', (42.0, True, False, None))
def test_show__fail_threshold__invalid_type(fail_threshold):
from pycobertura.cli import show

runner = CliRunner()
result = runner.invoke(
show,
['tests/dummy.original.xml', f'--fail-threshold={fail_threshold}'],
catch_exceptions=False,
)
assert result.output == f"""\
Usage: show [OPTIONS] COBERTURA_FILE
Try 'show --help' for help.

Error: Invalid value for '--fail-threshold': '{fail_threshold}' is not a valid integer range.
"""


def test_show__format_default():
Expand Down