Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create annotate only from diff coverage #188

Merged
merged 18 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
.DS_Store
.coverage
dev-env-vars
*.pyc
.venv
.vscode/settings.json
10 changes: 6 additions & 4 deletions coverage_comment/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
MISSING_LINES_GROUP_TITLE = "Annotations of lines with missing coverage"


def create_pr_annotations(annotation_type: str, coverage: coverage_module.Coverage):
def create_pr_annotations(annotation_type: str, coverage: coverage_module.DiffCoverage):
github.send_workflow_command(
command="group", command_value=MISSING_LINES_GROUP_TITLE
)

for filename, file_coverage in coverage.files.items():
for missing_line in file_coverage.missing_lines:
for filepath, file_coverage in coverage.files.items():
for missing_line in file_coverage.violation_lines:
github.create_missing_coverage_annotation(
annotation_type=annotation_type, file=filename, line=missing_line
annotation_type=annotation_type,
file=str(filepath),
line=missing_line,
)

github.send_workflow_command(command="endgroup", command_value="")
9 changes: 6 additions & 3 deletions coverage_comment/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class DiffCoverage:
total_num_violations: int
total_percent_covered: decimal.Decimal
num_changed_lines: int
files: dict[pathlib.Path, FileDiffCoverage]
files: dict[str, FileDiffCoverage]


def compute_coverage(num_covered: int, num_total: int) -> decimal.Decimal:
Expand Down Expand Up @@ -191,14 +191,17 @@ def extract_info(data) -> Coverage:
)


def get_diff_coverage_info(base_ref: str) -> DiffCoverage:
def get_diff_coverage_info(
base_ref: str, compare_to_origin: bool = True
) -> DiffCoverage:
subprocess.run("git", "fetch", "--depth=1000")
subprocess.run("coverage", "xml")
with tempfile.NamedTemporaryFile("r") as f:
ref_prefix = "origin/" if compare_to_origin else ""
subprocess.run(
"diff-cover",
"coverage.xml",
f"--compare-branch=origin/{base_ref}",
f"--compare-branch={ref_prefix}{base_ref}",
f"--json-report={f.name}",
"--diff-range-notation=..",
"--quiet",
Expand Down
13 changes: 9 additions & 4 deletions coverage_comment/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,22 @@ def action(

if event_name in {"pull_request", "push"}:
coverage = coverage_module.get_coverage_info(merge=config.MERGE_COVERAGE_FILES)

if event_name == "pull_request":
diff_coverage = coverage_module.get_diff_coverage_info(
base_ref=config.GITHUB_BASE_REF,
compare_to_origin=config.COV_DIFF_TO_ORIGIN,
)

if config.ANNOTATE_MISSING_LINES:
annotations.create_pr_annotations(
annotation_type=config.ANNOTATION_TYPE, coverage=coverage
annotation_type=config.ANNOTATION_TYPE, coverage=diff_coverage
)

return generate_comment(
config=config,
coverage=coverage,
diff_coverage=diff_coverage,
github_session=github_session,
)
else:
Expand All @@ -105,15 +112,13 @@ def action(
def generate_comment(
config: settings.Config,
coverage: coverage_module.Coverage,
diff_coverage: coverage_module.DiffCoverage,
github_session: httpx.Client,
) -> int:
log.info("Generating comment for PR")

gh = github_client.GitHub(session=github_session)

diff_coverage = coverage_module.get_diff_coverage_info(
base_ref=config.GITHUB_BASE_REF
)
previous_coverage_data_file = storage.get_datafile_contents(
github=gh,
repository=config.GITHUB_REPOSITORY,
Expand Down
1 change: 1 addition & 0 deletions coverage_comment/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class Config:
MERGE_COVERAGE_FILES: bool = False
ANNOTATE_MISSING_LINES: bool = False
ANNOTATION_TYPE: str = "warning"
COV_DIFF_TO_ORIGIN: bool = False
VERBOSE: bool = False
# Only for debugging, not exposed in the action:
FORCE_WORKFLOW_RUN: bool = False
Expand Down
94 changes: 34 additions & 60 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io
import os
import zipfile
from collections.abc import Callable

import httpx
import pytest
Expand Down Expand Up @@ -43,7 +44,7 @@ def _(**kwargs):


@pytest.fixture
def pull_request_config(base_config):
def pull_request_config(base_config) -> Callable:
def _(**kwargs):
defaults = {
# GitHub stuff
Expand Down Expand Up @@ -214,65 +215,6 @@ def coverage_obj_no_branch():
)


@pytest.fixture
def coverage_obj_many_missing_lines():
return coverage_module.Coverage(
meta=coverage_module.CoverageMetadata(
version="1.2.3",
timestamp=datetime.datetime(2000, 1, 1),
branch_coverage=True,
show_contexts=False,
),
info=coverage_module.CoverageInfo(
covered_lines=7,
num_statements=10,
percent_covered=decimal.Decimal("0.8"),
missing_lines=12,
excluded_lines=0,
num_branches=2,
num_partial_branches=1,
covered_branches=1,
missing_branches=1,
),
files={
"codebase/main.py": coverage_module.FileCoverage(
path="codebase/main.py",
executed_lines=[1, 2, 5, 6, 9],
missing_lines=[3, 7, 13, 21, 123],
excluded_lines=[],
info=coverage_module.CoverageInfo(
covered_lines=5,
num_statements=10,
percent_covered=decimal.Decimal("0.5"),
missing_lines=5,
excluded_lines=0,
num_branches=2,
num_partial_branches=1,
covered_branches=1,
missing_branches=1,
),
),
"codebase/caller.py": coverage_module.FileCoverage(
path="codebase/caller.py",
executed_lines=[1, 2, 5],
missing_lines=[13, 21, 212],
excluded_lines=[],
info=coverage_module.CoverageInfo(
covered_lines=3,
num_statements=6,
percent_covered=decimal.Decimal("0.5"),
missing_lines=3,
excluded_lines=0,
num_branches=2,
num_partial_branches=1,
covered_branches=1,
missing_branches=1,
),
),
},
)


@pytest.fixture
def diff_coverage_obj():
return coverage_module.DiffCoverage(
Expand All @@ -290,6 +232,38 @@ def diff_coverage_obj():
)


@pytest.fixture
def diff_coverage_obj_many_missing_lines():
return coverage_module.DiffCoverage(
total_num_lines=20,
total_num_violations=1,
total_percent_covered=decimal.Decimal("0.7"),
num_changed_lines=52,
files={
"codebase/code.py": coverage_module.FileDiffCoverage(
path="codebase/code.py",
percent_covered=decimal.Decimal("0.8"),
violation_lines=[3, 5, 21, 111],
),
"codebase/helper.py": coverage_module.FileDiffCoverage(
path="codebase/helper.py",
percent_covered=decimal.Decimal("0.6"),
violation_lines=[19, 22],
),
"codebase/log.py": coverage_module.FileDiffCoverage(
path="codebase/log.py",
percent_covered=decimal.Decimal("0.9"),
violation_lines=[],
),
"codebase/files.py": coverage_module.FileDiffCoverage(
path="codebase/files.py",
percent_covered=decimal.Decimal("0.8"),
violation_lines=[120, 121, 122],
),
},
)


@pytest.fixture
def session(is_failed):
"""
Expand Down
8 changes: 5 additions & 3 deletions tests/integration/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import pathlib
import subprocess
from collections.abc import Callable

import pytest

Expand All @@ -28,7 +29,7 @@ def file_path(integration_dir):


@pytest.fixture
def write_file(file_path):
def write_file(file_path) -> Callable:
def _(*variables):
content = "import os"
for i, var in enumerate(variables):
Expand Down Expand Up @@ -255,13 +256,14 @@ def test_action__pull_request__annotations(
)(status_code=200)

result = main.action(
config=pull_request_config(ANNOTATE_MISSING_LINES=True),
config=pull_request_config(
ANNOTATE_MISSING_LINES=True, COV_DIFF_TO_ORIGIN=False
),
github_session=session,
http_session=session,
git=None,
)
expected = """::group::Annotations of lines with missing coverage
::warning file=foo.py,line=6::This line has no coverage
::endgroup::"""
output = capsys.readouterr()

Expand Down
27 changes: 15 additions & 12 deletions tests/unit/test_annotations.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from coverage_comment import annotations


def test_annotations(coverage_obj, capsys):
annotations.create_pr_annotations(annotation_type="warning", coverage=coverage_obj)
def test_annotations(diff_coverage_obj, capsys):
annotations.create_pr_annotations(
annotation_type="warning", coverage=diff_coverage_obj
)

expected = """::group::Annotations of lines with missing coverage
::warning file=codebase/code.py,line=7::This line has no coverage
Expand All @@ -12,20 +14,21 @@ def test_annotations(coverage_obj, capsys):
assert output.out.strip() == expected


def test_annotations_several_files(coverage_obj_many_missing_lines, capsys):
def test_annotations_several_files(diff_coverage_obj_many_missing_lines, capsys):
annotations.create_pr_annotations(
annotation_type="notice", coverage=coverage_obj_many_missing_lines
annotation_type="notice", coverage=diff_coverage_obj_many_missing_lines
)

expected = """::group::Annotations of lines with missing coverage
::notice file=codebase/main.py,line=3::This line has no coverage
::notice file=codebase/main.py,line=7::This line has no coverage
::notice file=codebase/main.py,line=13::This line has no coverage
::notice file=codebase/main.py,line=21::This line has no coverage
::notice file=codebase/main.py,line=123::This line has no coverage
::notice file=codebase/caller.py,line=13::This line has no coverage
::notice file=codebase/caller.py,line=21::This line has no coverage
::notice file=codebase/caller.py,line=212::This line has no coverage
::notice file=codebase/code.py,line=3::This line has no coverage
::notice file=codebase/code.py,line=5::This line has no coverage
::notice file=codebase/code.py,line=21::This line has no coverage
::notice file=codebase/code.py,line=111::This line has no coverage
::notice file=codebase/helper.py,line=19::This line has no coverage
::notice file=codebase/helper.py,line=22::This line has no coverage
::notice file=codebase/files.py,line=120::This line has no coverage
::notice file=codebase/files.py,line=121::This line has no coverage
::notice file=codebase/files.py,line=122::This line has no coverage
::endgroup::"""
output = capsys.readouterr()
assert output.out.strip() == expected