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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ These files include:

See [an example](https://github.com/py-cov-action/python-coverage-comment-action-v3-example)

### Determining the mode

By default, the action will attempt to pick the appropriate mode based on the
current branch, whether or not it's in a pull request, and if that pull request
is open or closed. This frequently results in the correct action taking place,
but is only a heuristic. If you need more precise control, you should specify
the `ACTIVITY` parameter to directly choose the mode. It may be one of:

- `process_pr`, to select [PR mode](#pr-mode)
- `save_coverage_data_files`, to select [Default branch mode](#default-branch-mode)
- `post_comment`, to select [Commenting on the PR on the `push` event](#commenting-on-the-pr-on-the-push-event)

Combining this with [Github's Expressions]
(https://docs.github.com/en/actions/reference/workflows-and-actions/expressions) you can
build out the the custom handling needed. For example:

```yaml
- name: Coverage comment
id: coverage_comment
uses: py-cov-action/python-coverage-comment-action@v3
with:
GITHUB_TOKEN: ${{ github.token }}
activity: "${{ github.event_name == 'push' && 'save_coverage_data_files' || 'process_pr' }}"

# or

with:
activity: "${{ (github.event_name == 'push' && github.ref_name == 'main') && 'save_coverage_data_files' || 'process_pr' }}"
```

## Usage

### Setup
Expand Down Expand Up @@ -498,6 +528,10 @@ Usage may look like this

# Deprecated, see https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging
VERBOSE: false

# The specific activity that should be taken on this event, see
# [Determining the mode](#determining-the-mode) above.
ACTIVITY: ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who turns "" into None ? I think there should be some code somewhere doing that, and I'm not seeing it. Am I missing something or is it missing ?

```

### Commenting on the PR on the `push` event
Expand Down
20 changes: 16 additions & 4 deletions coverage_comment/activity.py → coverage_comment/activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,32 @@

from __future__ import annotations

from enum import Enum


class Activity(Enum):
PROCESS_PR = "process_pr"
POST_COMMENT = "post_comment"
SAVE_COVERAGE_DATA_FILES = "save_coverage_data_files"


class ActivityNotFound(Exception):
pass


class ActivityConfigError(Exception):
pass


def find_activity(
event_name: str,
is_default_branch: bool,
event_type: str | None,
is_pr_merged: bool,
) -> str:
) -> Activity:
"""Find the activity to perform based on the event type and payload."""
if event_name == "workflow_run":
return "post_comment"
return Activity.POST_COMMENT

if (
(event_name == "push" and is_default_branch)
Expand All @@ -31,9 +43,9 @@ def find_activity(
):
if event_name == "pull_request" and event_type == "closed" and not is_pr_merged:
raise ActivityNotFound
return "save_coverage_data_files"
return Activity.SAVE_COVERAGE_DATA_FILES

if event_name not in {"pull_request", "push", "merge_group"}:
raise ActivityNotFound

return "process_pr"
return Activity.PROCESS_PR
32 changes: 18 additions & 14 deletions coverage_comment/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import httpx

from coverage_comment import activity as activity_module
from coverage_comment import activities as activity_module
from coverage_comment import (
comment_file,
communication,
Expand Down Expand Up @@ -39,38 +39,42 @@ def action(
github=gh, repository=config.GITHUB_REPOSITORY
)
try:
activity = activity_module.find_activity(
event_name=event_name,
is_default_branch=repo_info.is_default_branch(ref=config.GITHUB_REF),
event_type=config.GITHUB_EVENT_TYPE,
is_pr_merged=config.IS_PR_MERGED,
)
activity = config.ACTIVITY
if not activity:
activity = activity_module.find_activity(
event_name=event_name,
is_default_branch=repo_info.is_default_branch(ref=config.GITHUB_REF),
event_type=config.GITHUB_EVENT_TYPE,
is_pr_merged=config.IS_PR_MERGED,
)
except activity_module.ActivityNotFound:
log.error(
'This action has only been designed to work for "pull_request", "push", '
f'"workflow_run", "schedule" or "merge_group" actions, not "{event_name}". Because there '
"are security implications. If you have a different usecase, please open an issue, "
"we'll be glad to add compatibility."
"This action's default behavior is to determine the appropriate "
"mode based on the current branch, whether or not it's in a pull "
"request, and if that pull request is open or closed. This "
"frequently results in the correct action taking place, but is "
"only a heuristic. If you need more precise control, you should "
'specify the "ACTIVITY" parameter as described in the documentation.'
)
return 1

if activity == "save_coverage_data_files":
if activity == activity_module.Activity.SAVE_COVERAGE_DATA_FILES:
return save_coverage_data_files(
config=config,
git=git,
http_session=http_session,
repo_info=repo_info,
)

elif activity == "process_pr":
elif activity == activity_module.Activity.PROCESS_PR:
return process_pr(
config=config,
gh=gh,
repo_info=repo_info,
)

else:
# activity == "post_comment":
# activity == activity_module.Activity.POST_COMMENT:
return post_comment(
config=config,
gh=gh,
Expand Down
9 changes: 7 additions & 2 deletions coverage_comment/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from coverage_comment import log

from . import json
from . import activities, json


class MissingEnvironmentVariable(Exception):
Expand Down Expand Up @@ -67,6 +67,7 @@ class Config:
ANNOTATION_TYPE: str = "warning"
MAX_FILES_IN_COMMENT: int = 25
USE_GH_PAGES_HTML_URL: bool = False
ACTIVITY: activities.Activity | None = None
VERBOSE: bool = False
# Only for debugging, not exposed in the action:
FORCE_WORKFLOW_RUN: bool = False
Expand Down Expand Up @@ -132,6 +133,10 @@ def clean_github_output(cls, value: str) -> pathlib.Path:
def clean_github_event_path(cls, value: str) -> pathlib.Path:
return pathlib.Path(value)

@classmethod
def clean_activity(cls, activity: str) -> activities.Activity:
return activities.Activity(activity)

@property
def GITHUB_PR_NUMBER(self) -> int | None:
# "refs/pull/2/merge"
Expand Down Expand Up @@ -181,7 +186,7 @@ def FINAL_COVERAGE_DATA_BRANCH(self):
# os.environ is, and just saying `dict[str, str]` is not enough to make
# mypy happy
@classmethod
def from_environ(cls, environ: MutableMapping[str, str]) -> Config:
def from_environ(cls, environ: MutableMapping[str, Any]) -> Config:
possible_variables = [e for e in inspect.signature(cls).parameters]
config: dict[str, Any] = {
k: v for k, v in environ.items() if k in possible_variables
Expand Down
73 changes: 71 additions & 2 deletions tests/integration/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from coverage_comment import main
from coverage_comment import activities, main

DIFF_STDOUT = """diff --git a/foo.py b/foo.py
index 6c08c94..b65c612 100644
Expand Down Expand Up @@ -61,7 +61,76 @@ def test_action__invalid_event_name(session, push_config, in_integration_env, ge
)

assert result == 1
assert get_logs("ERROR", "This action has only been designed to work for")
assert get_logs("ERROR", "This action's default behavior is to determine")


def test_action__explicit_activity(
session, workflow_run_config, in_integration_env, get_logs, zip_bytes
):
session.register(
"GET",
"/repos/py-cov-action/foobar",
json={"default_branch": "main", "visibility": "public"},
)
session.register("GET", "/user", json={"login": "foo"})
session.register(
"GET",
"/repos/py-cov-action/foobar/actions/runs/123",
json={
"head_branch": "branch",
"head_repository": {"owner": {"login": "bar/repo-name"}},
},
)

session.register(
"GET",
"/repos/py-cov-action/foobar/pulls",
match_params={
"head": "bar/repo-name:branch",
"sort": "updated",
"direction": "desc",
"state": "open",
},
json=[{"number": 456}],
)

session.register(
"GET",
"/repos/py-cov-action/foobar/actions/runs/123/artifacts",
match_params={"page": "1"},
json={
"artifacts": [{"name": "python-coverage-comment-action", "id": 789}],
"total_count": 1,
},
)

session.register(
"GET",
"/repos/py-cov-action/foobar/actions/artifacts/789/zip",
content=zip_bytes(
filename="python-coverage-comment-action.txt", content="Hey!"
),
)

session.register("GET", "/repos/py-cov-action/foobar/issues/456/comments", json=[])

session.register(
"POST",
"/repos/py-cov-action/foobar/issues/456/comments",
json={"body": "Hey!"},
)

result = main.action(
config=workflow_run_config(
GITHUB_EVENT_NAME="something_else",
ACTIVITY=activities.Activity.POST_COMMENT,
),
github_session=session,
http_session=session,
git=None,
)

assert result == 0


def get_expected_output(
Expand Down
40 changes: 26 additions & 14 deletions tests/unit/test_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,38 @@

import pytest

from coverage_comment import activity
from coverage_comment import activities


@pytest.mark.parametrize(
"event_name, is_default_branch, event_type, is_pr_merged, expected_activity",
[
("workflow_run", True, None, False, "post_comment"),
("push", True, None, False, "save_coverage_data_files"),
("push", False, None, False, "process_pr"),
("pull_request", True, "closed", True, "save_coverage_data_files"),
("pull_request", True, None, False, "process_pr"),
("pull_request", False, None, False, "process_pr"),
("schedule", False, None, False, "save_coverage_data_files"),
("merge_group", False, None, False, "save_coverage_data_files"),
("workflow_run", True, None, False, activities.Activity.POST_COMMENT),
("push", True, None, False, activities.Activity.SAVE_COVERAGE_DATA_FILES),
("push", False, None, False, activities.Activity.PROCESS_PR),
(
"pull_request",
True,
"closed",
True,
activities.Activity.SAVE_COVERAGE_DATA_FILES,
),
("pull_request", True, None, False, activities.Activity.PROCESS_PR),
("pull_request", False, None, False, activities.Activity.PROCESS_PR),
("schedule", False, None, False, activities.Activity.SAVE_COVERAGE_DATA_FILES),
(
"merge_group",
False,
None,
False,
activities.Activity.SAVE_COVERAGE_DATA_FILES,
),
],
)
def test_find_activity(
event_name, is_default_branch, event_type, is_pr_merged, expected_activity
):
result = activity.find_activity(
result = activities.find_activity(
event_name=event_name,
is_default_branch=is_default_branch,
event_type=event_type,
Expand All @@ -31,8 +43,8 @@ def test_find_activity(


def test_find_activity_not_found():
with pytest.raises(activity.ActivityNotFound):
activity.find_activity(
with pytest.raises(activities.ActivityNotFound):
activities.find_activity(
event_name="not_found",
is_default_branch=False,
event_type="not_found",
Expand All @@ -41,8 +53,8 @@ def test_find_activity_not_found():


def test_find_activity_pr_closed_not_merged():
with pytest.raises(activity.ActivityNotFound):
activity.find_activity(
with pytest.raises(activities.ActivityNotFound):
activities.find_activity(
event_name="pull_request",
is_default_branch=False,
event_type="closed",
Expand Down
6 changes: 4 additions & 2 deletions tests/unit/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import pytest

from coverage_comment import settings
from coverage_comment import activities, settings


@pytest.mark.parametrize("path", ["a", "a/b/.."])
Expand Down Expand Up @@ -51,6 +51,7 @@ def test_config__from_environ__ok():
"ANNOTATION_TYPE": "error",
"VERBOSE": "false",
"FORCE_WORKFLOW_RUN": "false",
"ACTIVITY": "process_pr",
}
) == settings.Config(
GITHUB_BASE_REF="master",
Expand All @@ -75,6 +76,7 @@ def test_config__from_environ__ok():
ANNOTATION_TYPE="error",
VERBOSE=False,
FORCE_WORKFLOW_RUN=False,
ACTIVITY=activities.Activity.PROCESS_PR,
)


Expand All @@ -86,7 +88,7 @@ def test_config__verbose_deprecated(get_logs):
"GITHUB_REPOSITORY": "owner/repo",
"GITHUB_REF": "master",
"GITHUB_EVENT_NAME": "pull",
"GITHUB_EVENT_PATH": pathlib.Path("test_event_path"),
"GITHUB_EVENT_PATH": "test_event_path",
"GITHUB_PR_RUN_ID": "123",
"GITHUB_STEP_SUMMARY": "step_summary",
"VERBOSE": "true",
Expand Down
Loading