Skip to content

Commit ac2675f

Browse files
committed
Add support for custom event dispatching
### Description This addresses #615 by adding the new EVENTS_AS_PR, EVENTS_AS_COVERAGE, and EVENTS_AS_COMMENT parameters. When any of these are specified, they override the default event-to-activity heuristic in #find_activity ### Testing I've added unit tests for these, but I haven't added integration or end-to-end tests, I was unable to get the environment set up correctly.
1 parent 1156b08 commit ac2675f

File tree

6 files changed

+229
-69
lines changed

6 files changed

+229
-69
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ These files include:
5959

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

62+
### Determining the mode
63+
64+
By default, the action will attempt to pick the appropriate mode based on the
65+
current branch, whether or not it's in a pull request, and if that pull request
66+
is open or closed. This frequently results in the correct action taking place,
67+
but is only a heuristic. If you need more precise control, you should specify
68+
the `EVENTS_AS_PR`, `EVENTS_AS_COVERAGE`, and `EVENTS_AS_COMMENT` parameters as
69+
described below. If any of these parameters are specified, they will be used
70+
instead of the default heuristic.
71+
6272
## Usage
6373

6474
### Setup
@@ -469,6 +479,23 @@ Usage may look like this
469479

470480
# Deprecated, see https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging
471481
VERBOSE: false
482+
483+
# List of Github Events which should be treated as Pull Requests,
484+
# analyzing the .coverage file as described in [PR Mode](#pr-mode)
485+
# above. Specified as a comma-separated list
486+
EVENTS_AS_PR: ""
487+
488+
# List of Github Events which should extract the coverage rate and store
489+
# information onto the dedicated coverage branch, as described in
490+
# [Default Branch Mode](#default-branch-mode) above. Specified as a
491+
# comma-separated list.
492+
EVENTS_AS_COVERAGE: ""
493+
494+
# List of Github Events which should result solely in comments being posted
495+
# to the pull request. See
496+
# [Commenting on the PR on the `push` event](#commenting-on-the-pr-on-the-push-event)
497+
# below. Specified as a comma-separated list.
498+
EVENTS_AS_COMMENT: ""
472499
```
473500
474501
### Commenting on the PR on the `push` event

coverage_comment/activity.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,48 @@
88

99
from __future__ import annotations
1010

11+
from enum import Enum
12+
13+
from coverage_comment import settings
14+
15+
16+
class Activity(Enum):
17+
PROCESS_PR = "process_pr"
18+
POST_COMMENT = "post_comment"
19+
SAVE_COVERAGE_DATA_FILES = "save_coverage_data_files"
1120

1221
class ActivityNotFound(Exception):
1322
pass
1423

24+
class ActivityConfigError(Exception):
25+
pass
26+
27+
def find_activity_from_config(
28+
event_name: str,
29+
config: settings.Config,
30+
) -> Activity:
31+
activities: list[Activity] = []
32+
if event_name in config.EVENTS_AS_PR:
33+
activities.append(Activity.PROCESS_PR)
34+
if event_name in config.EVENTS_AS_COVERAGE:
35+
activities.append(Activity.SAVE_COVERAGE_DATA_FILES)
36+
if event_name in config.EVENTS_AS_COMMENT:
37+
activities.append(Activity.POST_COMMENT)
38+
if len(activities) > 1:
39+
raise ActivityConfigError(f"Event {event_name} is specified in multiple EVENTS_AS_* variables: {', '.join([activity.value for activity in activities])}")
40+
if len(activities) == 0:
41+
raise ActivityNotFound(f"Event {event_name} is not specified in any of the EVENTS_AS_* variables")
42+
return activities[0]
1543

1644
def find_activity(
1745
event_name: str,
1846
is_default_branch: bool,
1947
event_type: str | None,
2048
is_pr_merged: bool,
21-
) -> str:
49+
) -> Activity:
2250
"""Find the activity to perform based on the event type and payload."""
2351
if event_name == "workflow_run":
24-
return "post_comment"
52+
return Activity.POST_COMMENT
2553

2654
if (
2755
(event_name == "push" and is_default_branch)
@@ -31,9 +59,9 @@ def find_activity(
3159
):
3260
if event_name == "pull_request" and event_type == "closed" and not is_pr_merged:
3361
raise ActivityNotFound
34-
return "save_coverage_data_files"
62+
return Activity.SAVE_COVERAGE_DATA_FILES
3563

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

39-
return "process_pr"
67+
return Activity.PROCESS_PR

coverage_comment/main.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,38 +74,47 @@ def action(
7474
github=gh, repository=config.GITHUB_REPOSITORY
7575
)
7676
try:
77-
activity = activity_module.find_activity(
78-
event_name=event_name,
79-
is_default_branch=repo_info.is_default_branch(ref=config.GITHUB_REF),
80-
event_type=config.GITHUB_EVENT_TYPE,
81-
is_pr_merged=config.IS_PR_MERGED,
82-
)
77+
if config.event_activities_specified:
78+
activity = activity_module.find_activity_from_config(
79+
event_name=event_name,
80+
config=config,
81+
)
82+
else:
83+
activity = activity_module.find_activity(
84+
event_name=event_name,
85+
is_default_branch=repo_info.is_default_branch(ref=config.GITHUB_REF),
86+
event_type=config.GITHUB_EVENT_TYPE,
87+
is_pr_merged=config.IS_PR_MERGED,
88+
)
8389
except activity_module.ActivityNotFound:
8490
log.error(
85-
'This action has only been designed to work for "pull_request", "push", '
86-
f'"workflow_run", "schedule" or "merge_group" actions, not "{event_name}". Because there '
87-
"are security implications. If you have a different usecase, please open an issue, "
88-
"we'll be glad to add compatibility."
91+
'This action\'s default behavior is to determine the appropriate '
92+
'mode based on the current branch, whether or not it\'s in a pull '
93+
'request, and if that pull request is open or closed. This '
94+
'frequently results in the correct action taking place, but is '
95+
'only a heuristic. If you need more precise control, you should '
96+
'specify the "EVENTS_AS_PR", "EVENTS_AS_COVERAGE", and '
97+
'"EVENTS_AS_COMMENT" parameters as described in the documentation.'
8998
)
9099
return 1
91100

92-
if activity == "save_coverage_data_files":
101+
if activity == activity_module.Activity.SAVE_COVERAGE_DATA_FILES:
93102
return save_coverage_data_files(
94103
config=config,
95104
git=git,
96105
http_session=http_session,
97106
repo_info=repo_info,
98107
)
99108

100-
elif activity == "process_pr":
109+
elif activity == activity_module.Activity.PROCESS_PR:
101110
return process_pr(
102111
config=config,
103112
gh=gh,
104113
repo_info=repo_info,
105114
)
106115

107116
else:
108-
# activity == "post_comment":
117+
# activity == activity_module.Activity.POST_COMMENT:
109118
return post_comment(
110119
config=config,
111120
gh=gh,

coverage_comment/settings.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ class Config:
6666
ANNOTATION_TYPE: str = "warning"
6767
MAX_FILES_IN_COMMENT: int = 25
6868
USE_GH_PAGES_HTML_URL: bool = False
69+
EVENTS_AS_PR: list[str] = dataclasses.field(default_factory=list)
70+
EVENTS_AS_COMMENT: list[str] = dataclasses.field(default_factory=list)
71+
EVENTS_AS_COVERAGE: list[str] = dataclasses.field(default_factory=list)
6972
VERBOSE: bool = False
7073
# Only for debugging, not exposed in the action:
7174
FORCE_WORKFLOW_RUN: bool = False
@@ -131,6 +134,18 @@ def clean_github_output(cls, value: str) -> pathlib.Path:
131134
def clean_github_event_path(cls, value: str) -> pathlib.Path:
132135
return pathlib.Path(value)
133136

137+
@classmethod
138+
def clean_events_as_pr(cls, value: str) -> list[str]:
139+
return [e.strip() for e in value.split(",") if e.strip()]
140+
141+
@classmethod
142+
def clean_events_as_comment(cls, value: str) -> list[str]:
143+
return [e.strip() for e in value.split(",") if e.strip()]
144+
145+
@classmethod
146+
def clean_events_as_coverage(cls, value: str) -> list[str]:
147+
return [e.strip() for e in value.split(",") if e.strip()]
148+
134149
@property
135150
def GITHUB_PR_NUMBER(self) -> int | None:
136151
# "refs/pull/2/merge"
@@ -176,6 +191,10 @@ def FINAL_COVERAGE_DATA_BRANCH(self):
176191
f"-{self.SUBPROJECT_ID}" if self.SUBPROJECT_ID else ""
177192
)
178193

194+
@property
195+
def event_activities_specified(self) -> bool:
196+
return bool(self.EVENTS_AS_COVERAGE) or bool(self.EVENTS_AS_PR) or bool(self.EVENTS_AS_COMMENT)
197+
179198
# We need to type environ as a MutableMapping because that's what
180199
# os.environ is, and just saying `dict[str, str]` is not enough to make
181200
# mypy happy

tests/unit/test_activity.py

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@
33
import pytest
44

55
from coverage_comment import activity
6+
from coverage_comment.settings import Config
67

78

89
@pytest.mark.parametrize(
910
"event_name, is_default_branch, event_type, is_pr_merged, expected_activity",
1011
[
11-
("workflow_run", True, None, False, "post_comment"),
12-
("push", True, None, False, "save_coverage_data_files"),
13-
("push", False, None, False, "process_pr"),
14-
("pull_request", True, "closed", True, "save_coverage_data_files"),
15-
("pull_request", True, None, False, "process_pr"),
16-
("pull_request", False, None, False, "process_pr"),
17-
("schedule", False, None, False, "save_coverage_data_files"),
18-
("merge_group", False, None, False, "save_coverage_data_files"),
12+
("workflow_run", True, None, False, activity.Activity.POST_COMMENT),
13+
("push", True, None, False, activity.Activity.SAVE_COVERAGE_DATA_FILES),
14+
("push", False, None, False, activity.Activity.PROCESS_PR),
15+
("pull_request", True, "closed", True, activity.Activity.SAVE_COVERAGE_DATA_FILES),
16+
("pull_request", True, None, False, activity.Activity.PROCESS_PR),
17+
("pull_request", False, None, False, activity.Activity.PROCESS_PR),
18+
("schedule", False, None, False, activity.Activity.SAVE_COVERAGE_DATA_FILES),
19+
("merge_group", False, None, False, activity.Activity.SAVE_COVERAGE_DATA_FILES),
1920
],
2021
)
2122
def test_find_activity(
@@ -48,3 +49,61 @@ def test_find_activity_pr_closed_not_merged():
4849
event_type="closed",
4950
is_pr_merged=False,
5051
)
52+
53+
BASE_ENV = {
54+
'GITHUB_BASE_REF': "",
55+
'GITHUB_TOKEN': "foo",
56+
'GITHUB_REPOSITORY': "owner/repo",
57+
'GITHUB_REF': "master",
58+
'GITHUB_EVENT_NAME': "pull_request",
59+
'GITHUB_PR_RUN_ID': 123,
60+
'GITHUB_STEP_SUMMARY': "step_summary",
61+
}
62+
63+
def test_find_activity_from_config__as_pr():
64+
config = Config.from_environ(BASE_ENV | {
65+
"EVENTS_AS_PR": "pull_request",
66+
"EVENTS_AS_COMMENT": "workflow_dispatch,workflow_call",
67+
"EVENTS_AS_COVERAGE": "push,merge_queue",
68+
})
69+
result = activity.find_activity_from_config(
70+
event_name="pull_request",
71+
config=config,
72+
)
73+
assert result == activity.Activity.PROCESS_PR
74+
75+
def test_find_activity_from_config__as_comment():
76+
config = Config.from_environ(BASE_ENV | {
77+
"EVENTS_AS_PR": "pull_request",
78+
"EVENTS_AS_COMMENT": "workflow_dispatch,workflow_call",
79+
"EVENTS_AS_COVERAGE": "push,merge_queue",
80+
})
81+
result = activity.find_activity_from_config(
82+
event_name="workflow_dispatch",
83+
config=config,
84+
)
85+
assert result == activity.Activity.POST_COMMENT
86+
87+
def test_find_activity_from_config__as_coverage():
88+
config = Config.from_environ(BASE_ENV | {
89+
"EVENTS_AS_PR": "pull_request",
90+
"EVENTS_AS_COMMENT": "workflow_dispatch,workflow_call",
91+
"EVENTS_AS_COVERAGE": "push,merge_queue",
92+
})
93+
result = activity.find_activity_from_config(
94+
event_name="push",
95+
config=config,
96+
)
97+
assert result == activity.Activity.SAVE_COVERAGE_DATA_FILES
98+
99+
def test_find_activity_from_config__multiple_activities():
100+
config = Config.from_environ(BASE_ENV | {
101+
"EVENTS_AS_PR": "pull_request",
102+
"EVENTS_AS_COMMENT": "workflow_dispatch,workflow_call",
103+
"EVENTS_AS_COVERAGE": "pull_request",
104+
})
105+
with pytest.raises(activity.ActivityConfigError):
106+
activity.find_activity_from_config(
107+
event_name="pull_request",
108+
config=config,
109+
)

0 commit comments

Comments
 (0)