diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3536c507..37539678 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,7 +18,7 @@ repos:
- id: check-added-large-files
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.6.3
+ rev: v0.6.9
hooks:
- id: ruff
args: [ --fix ]
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bbd6c822..8f817690 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,3 +64,16 @@ To run the end-to-end tests, you'll need:
- Please be aware that the tests will launch `gh auth setup-git` which might be
surprising if you use `https` remotes (sadly, setting `GIT_CONFIG_GLOBAL`
seems not to be enough to isolate tests.)
+
+## Coverage labs
+
+### Computing the coverage rate
+
+The coverage rate is `covered_lines / total_lines` (as one would expect).
+In case "branch coverage" is enabled, the coverage rate is
+`(covered_lines + covered_branches) / (total_lines + total_branches)`.
+In order to display coverage rates, we need to round the values. Depending on
+the situation, we either round to 0 or 2 decimal places. Rounding rules are:
+- We always round down (truncate) the value.
+- We don't display the trailing zeros in the decimal part (nor the decimal point
+ if the decimal part is 0).
diff --git a/coverage_comment/coverage.py b/coverage_comment/coverage.py
index 564649a5..591e9de9 100644
--- a/coverage_comment/coverage.py
+++ b/coverage_comment/coverage.py
@@ -13,7 +13,7 @@
# The dataclasses in this module are accessible in the template, which is overridable by the user.
# As a coutesy, we should do our best to keep the existing fields for backward compatibility,
# and if we really can't and can't add properties, at least bump the major version.
-@dataclasses.dataclass
+@dataclasses.dataclass(kw_only=True)
class CoverageMetadata:
version: str
timestamp: datetime.datetime
@@ -21,26 +21,28 @@ class CoverageMetadata:
show_contexts: bool
-@dataclasses.dataclass
+@dataclasses.dataclass(kw_only=True)
class CoverageInfo:
covered_lines: int
num_statements: int
percent_covered: decimal.Decimal
missing_lines: int
excluded_lines: int
- num_branches: int | None
- num_partial_branches: int | None
- covered_branches: int | None
- missing_branches: int | None
+ num_branches: int = 0
+ num_partial_branches: int = 0
+ covered_branches: int = 0
+ missing_branches: int = 0
-@dataclasses.dataclass
+@dataclasses.dataclass(kw_only=True)
class FileCoverage:
path: pathlib.Path
executed_lines: list[int]
missing_lines: list[int]
excluded_lines: list[int]
info: CoverageInfo
+ executed_branches: list[list[int]] | None = None
+ missing_branches: list[list[int]] | None = None
@dataclasses.dataclass
@@ -56,7 +58,7 @@ class Coverage:
# Maybe in v4, we can change it to a simpler format.
-@dataclasses.dataclass
+@dataclasses.dataclass(kw_only=True)
class FileDiffCoverage:
path: pathlib.Path
percent_covered: decimal.Decimal
@@ -73,7 +75,7 @@ def violation_lines(self) -> list[int]:
return self.missing_statements
-@dataclasses.dataclass
+@dataclasses.dataclass(kw_only=True)
class DiffCoverage:
total_num_lines: int
total_num_violations: int
@@ -82,10 +84,18 @@ class DiffCoverage:
files: dict[pathlib.Path, FileDiffCoverage]
-def compute_coverage(num_covered: int, num_total: int) -> decimal.Decimal:
- if num_total == 0:
+def compute_coverage(
+ num_covered: int,
+ num_total: int,
+ num_branches_covered: int = 0,
+ num_branches_total: int = 0,
+) -> decimal.Decimal:
+ """Compute the coverage percentage, with or without branch coverage."""
+ numerator = decimal.Decimal(num_covered + num_branches_covered)
+ denominator = decimal.Decimal(num_total + num_branches_total)
+ if denominator == 0:
return decimal.Decimal("1")
- return decimal.Decimal(num_covered) / decimal.Decimal(num_total)
+ return numerator / denominator
def get_coverage_info(
@@ -138,6 +148,26 @@ def generate_coverage_markdown(coverage_path: pathlib.Path) -> str:
)
+def _make_coverage_info(data: dict) -> CoverageInfo:
+ """Build a CoverageInfo object from a "summary" or "totals" key."""
+ return CoverageInfo(
+ covered_lines=data["covered_lines"],
+ num_statements=data["num_statements"],
+ percent_covered=compute_coverage(
+ num_covered=data["covered_lines"],
+ num_total=data["num_statements"],
+ num_branches_covered=data.get("covered_branches", 0),
+ num_branches_total=data.get("num_branches", 0),
+ ),
+ missing_lines=data["missing_lines"],
+ excluded_lines=data["excluded_lines"],
+ num_branches=data.get("num_branches", 0),
+ num_partial_branches=data.get("num_partial_branches", 0),
+ covered_branches=data.get("covered_branches", 0),
+ missing_branches=data.get("missing_branches", 0),
+ )
+
+
def extract_info(data: dict, coverage_path: pathlib.Path) -> Coverage:
"""
{
@@ -191,39 +221,13 @@ def extract_info(data: dict, coverage_path: pathlib.Path) -> Coverage:
excluded_lines=file_data["excluded_lines"],
executed_lines=file_data["executed_lines"],
missing_lines=file_data["missing_lines"],
- info=CoverageInfo(
- covered_lines=file_data["summary"]["covered_lines"],
- num_statements=file_data["summary"]["num_statements"],
- percent_covered=compute_coverage(
- file_data["summary"]["covered_lines"],
- file_data["summary"]["num_statements"],
- ),
- missing_lines=file_data["summary"]["missing_lines"],
- excluded_lines=file_data["summary"]["excluded_lines"],
- num_branches=file_data["summary"].get("num_branches"),
- num_partial_branches=file_data["summary"].get(
- "num_partial_branches"
- ),
- covered_branches=file_data["summary"].get("covered_branches"),
- missing_branches=file_data["summary"].get("missing_branches"),
- ),
+ executed_branches=file_data.get("executed_branches"),
+ missing_branches=file_data.get("missing_branches"),
+ info=_make_coverage_info(file_data["summary"]),
)
for path, file_data in data["files"].items()
},
- info=CoverageInfo(
- covered_lines=data["totals"]["covered_lines"],
- num_statements=data["totals"]["num_statements"],
- percent_covered=compute_coverage(
- data["totals"]["covered_lines"],
- data["totals"]["num_statements"],
- ),
- missing_lines=data["totals"]["missing_lines"],
- excluded_lines=data["totals"]["excluded_lines"],
- num_branches=data["totals"].get("num_branches"),
- num_partial_branches=data["totals"].get("num_partial_branches"),
- covered_branches=data["totals"].get("covered_branches"),
- missing_branches=data["totals"].get("missing_branches"),
- ),
+ info=_make_coverage_info(data["totals"]),
)
@@ -256,7 +260,8 @@ def get_diff_coverage_info(
total_num_violations += count_missing
percent_covered = compute_coverage(
- num_covered=count_executed, num_total=count_total
+ num_covered=count_executed,
+ num_total=count_total,
)
files[path] = FileDiffCoverage(
diff --git a/poetry.lock b/poetry.lock
index 397ffed1..5dc7e54b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2,13 +2,13 @@
[[package]]
name = "anyio"
-version = "4.4.0"
+version = "4.6.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
- {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
+ {file = "anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a"},
+ {file = "anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb"},
]
[package.dependencies]
@@ -16,9 +16,9 @@ idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
-doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
-test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
-trio = ["trio (>=0.23)"]
+doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"]
+trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
@@ -223,15 +223,18 @@ files = [
[[package]]
name = "idna"
-version = "3.8"
+version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
files = [
- {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"},
- {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
+ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+ {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
+[package.extras]
+all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
[[package]]
name = "iniconfig"
version = "2.0.0"
@@ -414,13 +417,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pytest"
-version = "8.3.2"
+version = "8.3.3"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
- {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
+ {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
+ {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
]
[package.dependencies]
@@ -469,29 +472,29 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
name = "ruff"
-version = "0.6.3"
+version = "0.6.9"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3"},
- {file = "ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc"},
- {file = "ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500"},
- {file = "ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470"},
- {file = "ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f"},
- {file = "ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5"},
- {file = "ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351"},
- {file = "ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8"},
- {file = "ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521"},
- {file = "ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb"},
- {file = "ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82"},
- {file = "ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983"},
+ {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
+ {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
+ {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
+ {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
+ {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
+ {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
+ {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
+ {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
+ {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
]
[[package]]
diff --git a/tests/conftest.py b/tests/conftest.py
index 7aead5fe..84ac63cf 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -282,10 +282,10 @@ def _(code: str, has_branches: bool = True) -> coverage_module.Coverage:
percent_covered=decimal.Decimal("1.0"),
missing_lines=0,
excluded_lines=0,
- num_branches=0 if has_branches else None,
- num_partial_branches=0 if has_branches else None,
- covered_branches=0 if has_branches else None,
- missing_branches=0 if has_branches else None,
+ num_branches=0,
+ num_partial_branches=0,
+ covered_branches=0,
+ missing_branches=0,
),
files={},
)
@@ -313,10 +313,10 @@ def _(code: str, has_branches: bool = True) -> coverage_module.Coverage:
percent_covered=decimal.Decimal("1.0"),
missing_lines=0,
excluded_lines=0,
- num_branches=0 if has_branches else None,
- num_partial_branches=0 if has_branches else None,
- covered_branches=0 if has_branches else None,
- missing_branches=0 if has_branches else None,
+ num_branches=0,
+ num_partial_branches=0,
+ covered_branches=0,
+ missing_branches=0,
),
)
if set(line.split()) & {
@@ -340,7 +340,6 @@ def _(code: str, has_branches: bool = True) -> coverage_module.Coverage:
coverage_obj.files[current_file].excluded_lines.append(line_number)
coverage_obj.files[current_file].info.excluded_lines += 1
coverage_obj.info.excluded_lines += 1
-
if has_branches and "branch" in line:
coverage_obj.files[current_file].info.num_branches += 1
coverage_obj.info.num_branches += 1
@@ -353,21 +352,22 @@ def _(code: str, has_branches: bool = True) -> coverage_module.Coverage:
elif "branch missing" in line:
coverage_obj.files[current_file].info.missing_branches += 1
coverage_obj.info.missing_branches += 1
-
info = coverage_obj.files[current_file].info
coverage_obj.files[
current_file
].info.percent_covered = coverage_module.compute_coverage(
num_covered=info.covered_lines,
num_total=info.num_statements,
+ num_branches_covered=info.covered_branches,
+ num_branches_total=info.num_branches,
)
-
info = coverage_obj.info
coverage_obj.info.percent_covered = coverage_module.compute_coverage(
num_covered=info.covered_lines,
num_total=info.num_statements,
+ num_branches_covered=info.covered_branches,
+ num_branches_total=info.num_branches,
)
-
return coverage_obj
return _
@@ -425,9 +425,19 @@ def coverage_code():
9
10 branch missing
11 missing
- 12
+ 12 covered
13 branch covered
14 covered
+ 15 branch partial
+ 16 branch covered
+ 17 branch missing
+ 18 covered
+ 19 covered
+ 20 branch partial
+ 21 branch missing
+ 22 branch covered
+ 23 branch covered
+ 24 branch covered
"""
@@ -442,32 +452,48 @@ def coverage_json():
},
"files": {
"codebase/code.py": {
- "executed_lines": [1, 2, 3, 5, 13, 14],
+ "executed_lines": [
+ 1,
+ 2,
+ 3,
+ 5,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 18,
+ 19,
+ 20,
+ 22,
+ 23,
+ 24,
+ ],
"summary": {
- "covered_lines": 6,
- "num_statements": 10,
- "percent_covered": 60.0,
- "missing_lines": 4,
+ "covered_lines": 15,
+ "num_statements": 21,
+ "percent_covered": 0.625,
+ "missing_lines": 6,
"excluded_lines": 0,
- "num_branches": 3,
- "num_partial_branches": 1,
- "covered_branches": 1,
- "missing_branches": 1,
+ "num_branches": 11,
+ "num_partial_branches": 3,
+ "covered_branches": 5,
+ "missing_branches": 3,
},
- "missing_lines": [6, 8, 10, 11],
+ "missing_lines": [6, 8, 10, 11, 17, 21],
"excluded_lines": [],
}
},
"totals": {
- "covered_lines": 6,
- "num_statements": 10,
- "percent_covered": 60.0,
- "missing_lines": 4,
+ "covered_lines": 15,
+ "num_statements": 21,
+ "percent_covered": 0.625,
+ "missing_lines": 6,
"excluded_lines": 0,
- "num_branches": 3,
- "num_partial_branches": 1,
- "covered_branches": 1,
- "missing_branches": 1,
+ "num_branches": 11,
+ "num_partial_branches": 3,
+ "covered_branches": 5,
+ "missing_branches": 3,
},
}
diff --git a/tests/unit/test_coverage.py b/tests/unit/test_coverage.py
index 6ea249a2..621219da 100644
--- a/tests/unit/test_coverage.py
+++ b/tests/unit/test_coverage.py
@@ -39,6 +39,24 @@ def test_compute_coverage(num_covered, num_total, expected_coverage):
)
+@pytest.mark.parametrize(
+ "num_covered, num_total, branch_covered, branch_total, expected_coverage",
+ [
+ (0, 10, 0, 15, "0"),
+ (0, 0, 0, 0, "1"),
+ (5, 0, 5, 0, "1"),
+ (5, 10, 5, 10, "0.5"),
+ (1, 50, 1, 50, "0.02"),
+ ],
+)
+def test_compute_coverage_with_branches(
+ num_covered, num_total, branch_covered, branch_total, expected_coverage
+):
+ assert coverage.compute_coverage(
+ num_covered, num_total, branch_covered, branch_total
+ ) == decimal.Decimal(expected_coverage)
+
+
def test_get_coverage_info(mocker, coverage_json, coverage_obj):
run = mocker.patch(
"coverage_comment.subprocess.run", return_value=json.dumps(coverage_json)
@@ -132,6 +150,42 @@ def test_generate_coverage_markdown(mocker):
assert result == "foo"
+def test__make_coverage_info():
+ result = coverage._make_coverage_info(
+ {
+ "covered_lines": 14,
+ "num_statements": 20,
+ "missing_lines": 6,
+ "excluded_lines": 0,
+ }
+ )
+ assert isinstance(result, coverage.CoverageInfo)
+ assert result.percent_covered == decimal.Decimal(14) / decimal.Decimal(20)
+ assert result.num_branches == 0
+ assert result.num_partial_branches == 0
+ assert result.covered_branches == 0
+ assert result.missing_branches == 0
+
+
+def test__make_coverage_info__with_branches():
+ result = coverage._make_coverage_info(
+ {
+ "covered_lines": 4,
+ "num_statements": 10,
+ "missing_lines": 1,
+ "excluded_lines": 0,
+ "covered_branches": 4,
+ "num_branches": 6,
+ "num_partial_branches": 2,
+ }
+ )
+ assert isinstance(result, coverage.CoverageInfo)
+ assert result.percent_covered == decimal.Decimal(4 + 4) / decimal.Decimal(10 + 6)
+ assert result.covered_branches == 4
+ assert result.missing_branches == 0
+ assert result.excluded_lines == 0
+
+
@pytest.mark.parametrize(
"added_lines, update_obj, expected",
[
diff --git a/tests/unit/test_template.py b/tests/unit/test_template.py
index 62820423..521b5dc8 100644
--- a/tests/unit/test_template.py
+++ b/tests/unit/test_template.py
@@ -44,7 +44,7 @@ def test_get_comment_markdown(coverage_obj, diff_coverage_obj):
.split(maxsplit=4)
)
- expected = ["92%", "60%", "50%", "bar", ""]
+ expected = ["92%", "62.5%", "60%", "bar", ""]
assert result == expected
@@ -79,17 +79,17 @@ def test_template(coverage_obj, diff_coverage_obj):
expected = """## Coverage report (foo)
-

Click to see where and how coverage changed
+

Click to see where and how coverage changed
File | Statements | Missing | Coverage | Coverage (new stmts) | Lines missing |
codebase |
code.py |
- |  |  |  | 6-8 |
+ |  |  |  | 6-8 |
Project Total |
- |  |  |  | |
+ |  |  |  | |
@@ -264,17 +264,17 @@ def test_template__no_previous(coverage_obj_no_branch, diff_coverage_obj):
expected = """## Coverage report
-

Click to see where and how coverage changed
+

Click to see where and how coverage changed
File | Statements | Missing | Coverage | Coverage (new stmts) | Lines missing |
codebase |
code.py |
- |  |  |  | 6-8 |
+ |  |  |  | 6-8 |
Project Total |
- |  |  |  | |
+ |  |  |  | |