|
| 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"[]({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 | + |
0 commit comments