Skip to content

Commit 2c39db9

Browse files
authored
Run tests workflow: add comments on start (#29875)
1 parent e3d52d4 commit 2c39db9

File tree

12 files changed

+340
-34
lines changed

12 files changed

+340
-34
lines changed

.github/actions/build_and_test_ya/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ inputs:
6363
type: boolean
6464
default: false
6565
description: "Collect coredumps via enabling ulimit -c unlimited"
66+
pull_number:
67+
type: string
68+
required: false
69+
description: "Pull request number (for workflow_call events)"
6670
defaults:
6771
run:
6872
shell: bash
@@ -124,3 +128,4 @@ runs:
124128
telegram_alert_logins: ${{ fromJSON( inputs.vars ).GH_ALERTS_TG_LOGINS || '' }}
125129
telegram_alert_chat: ${{ fromJSON( inputs.vars ).GH_ALERTS_TG_CHAT || '' }}
126130
collect_coredumps: ${{ inputs.collect_coredumps }}
131+
pull_number: ${{ inputs.pull_number }}

.github/actions/test_ya/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ inputs:
7575
type: boolean
7676
default: false
7777
description: "Collect coredumps via enabling ulimit -c unlimited"
78+
pull_number:
79+
type: string
80+
required: false
81+
description: "Pull request number (for workflow_call events)"
7882

7983
outputs:
8084
success:
@@ -153,6 +157,7 @@ runs:
153157
TELEGRAM_BOT_TOKEN: ${{ inputs.telegram_ydbot_token }}
154158
GH_ALERTS_TG_LOGINS: ${{ inputs.telegram_alert_logins }}
155159
GH_ALERTS_TG_CHAT: ${{ inputs.telegram_alert_chat }}
160+
PR_NUMBER: ${{ inputs.pull_number }}
156161
run: |
157162
set -ex
158163
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Script to add run tests table comment to PR.
4+
Used by add_run_tests_table.yml workflow.
5+
"""
6+
7+
import os
8+
import json
9+
import urllib.parse
10+
from github import Github, Auth as GithubAuth
11+
12+
13+
def normalize_app_domain(app_domain: str) -> str:
14+
"""Normalize app domain - remove https:// prefix if present."""
15+
domain = app_domain.strip()
16+
if domain.startswith("https://"):
17+
domain = domain[8:]
18+
if domain.startswith("http://"):
19+
domain = domain[7:]
20+
return domain.rstrip('/')
21+
22+
23+
def generate_run_tests_table(pr_number: int, app_domain: str) -> str:
24+
"""Generate run tests execution table with buttons for different build presets."""
25+
domain = normalize_app_domain(app_domain)
26+
base_url = f"https://{domain}/workflow/trigger"
27+
repo_env = os.environ.get("GITHUB_REPOSITORY")
28+
if not repo_env or "/" not in repo_env:
29+
raise ValueError("GITHUB_REPOSITORY environment variable is not set or malformed (expected 'owner/repo')")
30+
owner, repo = repo_env.split("/", 1)
31+
workflow_id = "run_tests.yml"
32+
return_url = f"https://github.com/{owner}/{repo}/pull/{pr_number}"
33+
34+
# Build presets with their badge colors
35+
build_presets = [
36+
{"name": "relwithdebinfo", "badge_color": "4caf50"},
37+
{"name": "release-asan", "badge_color": "ff9800"},
38+
{"name": "release-msan", "badge_color": "2196F3"},
39+
{"name": "release-tsan", "badge_color": "9c27b0"}
40+
]
41+
42+
# Build HTML table: Build Preset with Run tests button
43+
rows = []
44+
for preset in build_presets:
45+
# Generate URL for this preset (all test sizes included)
46+
params = {
47+
"owner": owner,
48+
"repo": repo,
49+
"workflow_id": workflow_id,
50+
"ref": "main",
51+
"pull_number": str(pr_number),
52+
"test_targets": "ydb/",
53+
"test_type": "unittest,py3test,py2test,pytest",
54+
"test_size": "small,medium,large", # All test sizes
55+
"additional_ya_make_args": "",
56+
"build_preset": preset["name"],
57+
"collect_coredumps": "false",
58+
"return_url": return_url
59+
}
60+
query_string = "&".join([f"{k}={urllib.parse.quote(str(v), safe='')}" for k, v in params.items()])
61+
url_ui = f"{base_url}?{query_string}&ui=true"
62+
63+
# Badge with only message (no label) - format: badge/message-color
64+
# Encode only spaces, keep emoji as is - use two spaces like in backport
65+
badge_text = "▶ Run tests".replace(" ", "%20")
66+
button = f"[![▶ Run tests](https://img.shields.io/badge/{badge_text}-{preset['badge_color']})]({url_ui})"
67+
rows.append(f"| `{preset['name']}` | {button} |")
68+
69+
table = "<!-- run-tests-table -->\n"
70+
table += "<h3>Run Tests</h3>\n\n"
71+
table += "| Build Preset | Run |\n"
72+
table += "|--------|-----|\n"
73+
table += "\n".join(rows)
74+
return table
75+
76+
77+
def create_or_update_pr_comment(pr, app_domain: str) -> None:
78+
"""Create or update run tests table comment on PR.
79+
80+
Args:
81+
pr: GitHub PullRequest object
82+
app_domain: Application domain for workflow URLs
83+
"""
84+
try:
85+
pr_number = pr.number
86+
87+
run_tests_table = generate_run_tests_table(pr_number, app_domain)
88+
header = "<!-- run-tests-table -->"
89+
90+
# Check if comment with run tests table already exists
91+
existing_comment = None
92+
for comment in pr.get_issue_comments():
93+
if comment.body.startswith(header):
94+
existing_comment = comment
95+
break
96+
97+
if existing_comment:
98+
# Update existing comment
99+
existing_comment.edit(run_tests_table)
100+
print(f"::notice::Updated run tests table comment on PR #{pr_number}")
101+
else:
102+
# Create new comment
103+
pr.create_issue_comment(run_tests_table)
104+
print(f"::notice::Created run tests table comment on PR #{pr_number}")
105+
except Exception as e:
106+
print(f"::error::Failed to create/update comment on PR #{pr_number}: {e}")
107+
raise
108+
109+
110+
def main():
111+
"""Main function to add run tests table to PR."""
112+
# Get PR info - either from event or from workflow_dispatch input
113+
pr_number_from_input = os.environ.get("PR_NUMBER")
114+
github_token = os.environ.get("GITHUB_TOKEN")
115+
github_repo = os.environ.get("GITHUB_REPOSITORY")
116+
117+
if not github_token:
118+
raise ValueError("GITHUB_TOKEN environment variable is not set")
119+
if not github_repo:
120+
raise ValueError("GITHUB_REPOSITORY environment variable is not set")
121+
122+
gh = Github(auth=GithubAuth.Token(github_token))
123+
repo = gh.get_repo(github_repo)
124+
125+
if pr_number_from_input:
126+
# workflow_dispatch mode - get PR by number
127+
pr_number = int(pr_number_from_input)
128+
pr = repo.get_pull(pr_number)
129+
print(f"::notice::workflow_dispatch mode: Adding run tests table to PR #{pr_number}")
130+
else:
131+
# pull_request event mode - get PR from event
132+
event_path = os.environ.get("GITHUB_EVENT_PATH")
133+
if not event_path:
134+
raise ValueError("GITHUB_EVENT_PATH environment variable is not set")
135+
136+
if not os.path.exists(event_path):
137+
raise FileNotFoundError(f"Event file not found: {event_path}")
138+
139+
with open(event_path, 'r') as f:
140+
event = json.load(f)
141+
142+
if "pull_request" not in event:
143+
raise ValueError("Event does not contain pull_request data")
144+
145+
pr_number = event["pull_request"]["number"]
146+
pr = repo.get_pull(pr_number)
147+
148+
app_domain = os.environ.get("APP_DOMAIN")
149+
if not app_domain:
150+
raise ValueError("APP_DOMAIN environment variable is not set")
151+
152+
create_or_update_pr_comment(pr, app_domain)
153+
154+
155+
if __name__ == "__main__":
156+
main()
157+

.github/scripts/tests/comment-pr.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from github.PullRequest import PullRequest
88

99

10-
def update_pr_comment_text(pr: PullRequest, build_preset: str, run_number: int, color: str, text: str, rewrite: bool):
10+
def update_pr_comment_text(pr: PullRequest, build_preset: str, run_number: int, color: str, text: str, rewrite: bool, no_timestamp: bool = False):
1111
header = f"<!-- status pr={pr.number}, preset={build_preset}, run={run_number} -->"
1212

1313
body = comment = None
@@ -22,9 +22,12 @@ def update_pr_comment_text(pr: PullRequest, build_preset: str, run_number: int,
2222
if body is None:
2323
body = [header]
2424

25-
indicator = f":{color}_circle:"
26-
timestamp_str = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
27-
body.append(f"{indicator} `{timestamp_str}` {text}")
25+
if no_timestamp:
26+
body.append(text)
27+
else:
28+
indicator = f":{color}_circle:"
29+
timestamp_str = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
30+
body.append(f"{indicator} `{timestamp_str}` {text}")
2831

2932
body = "\n".join(body)
3033

@@ -40,29 +43,50 @@ def main():
4043
parser = argparse.ArgumentParser()
4144
parser.add_argument("--rewrite", dest="rewrite", action="store_true")
4245
parser.add_argument("--color", dest="color", default="white")
46+
parser.add_argument("--no-timestamp", dest="no_timestamp", action="store_true", help="Skip adding timestamp to comment")
4347
parser.add_argument("text", type=argparse.FileType("r"), nargs="?", default="-")
4448

4549
args = parser.parse_args()
4650
color = args.color
4751

48-
run_number = int(os.environ.get("GITHUB_RUN_NUMBER"))
52+
run_number = int(os.environ.get("GITHUB_RUN_NUMBER", os.environ.get("GITHUB_RUN_ID", "0")))
4953
build_preset = os.environ["BUILD_PRESET"]
5054

5155
gh = Github(auth=GithubAuth.Token(os.environ["GITHUB_TOKEN"]))
56+
57+
# Try to get PR from event or from PR_NUMBER env var
58+
pr = None
59+
event_name = os.environ.get('GITHUB_EVENT_NAME', '')
60+
61+
if event_name.startswith('pull_request'):
62+
# Standard pull_request event
63+
with open(os.environ["GITHUB_EVENT_PATH"]) as fp:
64+
event = json.load(fp)
65+
pr = gh.create_from_raw_data(PullRequest, event["pull_request"])
66+
else:
67+
# workflow_call or workflow_dispatch - try to get PR_NUMBER from env
68+
# PR_NUMBER is just the number, e.g. "12345"
69+
pr_number = os.environ.get("PR_NUMBER")
70+
if pr_number:
71+
try:
72+
repo = gh.get_repo(os.environ["GITHUB_REPOSITORY"])
73+
pr = repo.get_pull(int(pr_number))
74+
except Exception as e:
75+
print(f"::warning::Failed to get PR {pr_number}: {e}")
76+
return
77+
78+
if pr is None:
79+
print("::warning::No PR found, skipping comment")
80+
return
5281

53-
with open(os.environ["GITHUB_EVENT_PATH"]) as fp:
54-
event = json.load(fp)
55-
56-
pr = gh.create_from_raw_data(PullRequest, event["pull_request"])
5782
text = args.text.read()
5883
if text.endswith("\n"):
5984
# dirty hack because echo adds a new line
6085
# and 'echo | comment-pr.py' leads to an extra newline
6186
text = text[:-1]
6287

63-
update_pr_comment_text(pr, build_preset, run_number, color, text, args.rewrite)
88+
update_pr_comment_text(pr, build_preset, run_number, color, text, args.rewrite, args.no_timestamp)
6489

6590

6691
if __name__ == "__main__":
67-
if os.environ.get('GITHUB_EVENT_NAME', '').startswith('pull_request'):
68-
main()
92+
main()
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Add run tests table to PR
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
workflow_dispatch:
8+
inputs:
9+
pr_number:
10+
description: 'Pull Request number to add run tests table to'
11+
required: true
12+
type: number
13+
14+
permissions:
15+
contents: read
16+
pull-requests: write
17+
18+
env:
19+
GH_TOKEN: ${{ secrets.YDBOT_TOKEN }}
20+
21+
jobs:
22+
add-run-tests-table:
23+
name: Add run tests table to PR
24+
runs-on: [ self-hosted, auto-provisioned, build-preset-analytic-node]
25+
if: >
26+
(
27+
github.event_name == 'workflow_dispatch'
28+
) ||
29+
(
30+
github.event_name == 'pull_request_target' &&
31+
vars.SHOW_RUN_TESTS_IN_PR == 'TRUE'
32+
)
33+
34+
steps:
35+
- name: Checkout repository
36+
uses: actions/checkout@v4
37+
with:
38+
fetch-depth: 1
39+
sparse-checkout: |
40+
.github
41+
42+
- name: Install dependencies
43+
run: |
44+
python3 -m pip install --upgrade pip
45+
pip install PyGithub
46+
47+
- name: Add run tests table to PR
48+
env:
49+
GITHUB_TOKEN: ${{ env.GH_TOKEN }}
50+
GITHUB_EVENT_PATH: ${{ github.event_path }}
51+
GITHUB_REPOSITORY: ${{ github.repository }}
52+
GITHUB_WORKSPACE: ${{ github.workspace }}
53+
APP_DOMAIN: ${{ vars.APP_DOMAIN }}
54+
PR_NUMBER: ${{ github.event_name == 'workflow_dispatch' && inputs.pr_number || '' }}
55+
run: |
56+
python3 ./.github/scripts/add_run_tests_table.py
57+

.github/workflows/regression_run.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ on:
77
workflow_dispatch:
88
inputs:
99
pull_request_input:
10-
description: 'Pull request, overrides default branches, use with pull/* prefix, ex. pull/12345'
10+
description: 'Pull request number (overrides use_default_branches), ex: 12345'
1111
required: false
1212
default: ''
1313
use_default_branches:
14-
description: 'If true, start main and all current stable branches. If false, start only the selected branch.'
14+
description: 'If true, start main and all current stable branches. If false, start only the selected branch. Ignored if pull_request_input is set.'
1515
type: boolean
1616
required: false
1717
default: true
@@ -26,6 +26,6 @@ jobs:
2626
build_preset: ["relwithdebinfo", "release-asan", "release-tsan", "release-msan"]
2727
with:
2828
test_targets: ydb/
29-
branches: ${{ inputs.pull_request_input || (startsWith(inputs.use_default_branches, 'false') && github.ref_name || '') }}
29+
branches: ${{ inputs.pull_request_input && format('pull/{0}', inputs.pull_request_input) || (startsWith(inputs.use_default_branches, 'false') && github.ref_name || '') }}
3030
branches_config_path: '.github/config/stable_tests_branches.json'
3131
build_preset: ${{ matrix.build_preset }}

.github/workflows/regression_run_compatibility.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111
workflow_dispatch:
1212
inputs:
1313
pull_request_input:
14-
description: 'Pull request, use with pull/* prefix, ex. pull/12345'
14+
description: 'Pull request number (overrides default branches), ex: 12345'
1515
required: false
1616
default: ''
1717
initial_version_ref:
@@ -37,7 +37,7 @@ jobs:
3737
build_preset: ["relwithdebinfo", "release-asan", "release-tsan", "release-msan"]
3838
with:
3939
test_targets: ydb/tests/compatibility/
40-
branches: ${{ inputs.pull_request_input || github.ref_name }}
40+
branches: ${{ inputs.pull_request_input && format('pull/{0}', inputs.pull_request_input) || github.ref_name }}
4141
build_preset: ${{ matrix.build_preset }}
4242
additional_ya_make_args: >-
4343
${{

.github/workflows/regression_run_large.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ on:
66
workflow_dispatch:
77
inputs:
88
pull_request_input:
9-
description: 'Pull request, overrides default branches, use with pull/* prefix, ex. pull/12345'
9+
description: 'Pull request number (overrides use_default_branches), ex: 12345'
1010
required: false
1111
default: ''
1212
use_default_branches:
13-
description: 'If true, start main and all current stable branches. If false, start only the selected branch.'
13+
description: 'If true, start main and all current stable branches. If false, start only the selected branch. Ignored if pull_request_input is set.'
1414
type: boolean
1515
required: false
1616
default: true
@@ -26,6 +26,6 @@ jobs:
2626
with:
2727
test_targets: ydb/
2828
test_size: large
29-
branches: ${{ inputs.pull_request_input || (startsWith(inputs.use_default_branches, 'false') && github.ref_name || '') }}
29+
branches: ${{ inputs.pull_request_input && format('pull/{0}', inputs.pull_request_input) || (startsWith(inputs.use_default_branches, 'false') && github.ref_name || '') }}
3030
branches_config_path: '.github/config/stable_tests_branches.json'
3131
build_preset: ${{ matrix.build_preset }}

0 commit comments

Comments
 (0)