Skip to content
Open
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
184 changes: 184 additions & 0 deletions .github/actions/run_tests/pr_comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""
Script to create and update PR comments for test runs.
"""
import os
import sys
from github import Github, Auth as GithubAuth

def get_pr_number():
"""Extract PR number from environment variable."""
pr_number = os.environ.get("PR_NUMBER")
if not pr_number:
raise ValueError("PR_NUMBER environment variable is not set")

# Remove pull/ prefix if present
if pr_number.startswith("pull/"):
pr_number = pr_number.replace("pull/", "")

return int(pr_number)

def get_workflow_run_url():
"""Get workflow run URL for identification."""
github_server = os.environ.get("GITHUB_SERVER_URL")
if not github_server:
raise ValueError("GITHUB_SERVER_URL environment variable is not set")

github_repo = os.environ.get("GITHUB_REPOSITORY")
if not github_repo:
raise ValueError("GITHUB_REPOSITORY environment variable is not set")

run_id = os.environ.get("GITHUB_RUN_ID")
if not run_id:
raise ValueError("GITHUB_RUN_ID environment variable is not set")

return f"{github_server}/{github_repo}/actions/runs/{run_id}"
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The PR comment logic identifies comments by workflow run URL (line 35), but the "Update PR comment with results" step runs within the matrix job (lines 245-256 in run_tests.yml). Since all matrix jobs share the same GITHUB_RUN_ID, they will all try to update the same comment. This will cause the last completing matrix job to overwrite the results from other jobs, losing information about tests for other branches. Consider either:

  1. Creating separate comments per matrix branch by including the branch in the identifier
  2. Moving the update step to a separate job that runs after all matrix jobs complete and aggregates results
  3. Including the matrix branch context in the comment identification and creating one comment per branch

Copilot uses AI. Check for mistakes.

def create_or_update_comment(pr_number, message, workflow_run_url):
"""Create or update PR comment with test run information."""
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
raise ValueError("GITHUB_TOKEN environment variable is not set")

github_repo = os.environ.get("GITHUB_REPOSITORY")
if not github_repo:
raise ValueError("GITHUB_REPOSITORY environment variable is not set")

gh = Github(auth=GithubAuth.Token(github_token))
repo = gh.get_repo(github_repo)
pr = repo.get_pull(pr_number)

# Find existing comment by workflow run URL
comment = None
for c in pr.get_issue_comments():
if workflow_run_url in c.body:
comment = c
break

# Add workflow run link to message
full_body = f"{message}\n\n[View workflow run]({workflow_run_url})"

if comment:
print(f"::notice::Updating existing comment id={comment.id}")
try:
comment.edit(full_body)
except Exception as e:
print(f"::error::Failed to update comment id={comment.id}: {e}", file=sys.stderr)
raise
else:
print(f"::notice::Creating new comment")
try:
pr.create_issue_comment(full_body)
except Exception as e:
print(f"::error::Failed to create new comment: {e}", file=sys.stderr)
raise

def format_start_message(build_preset, test_size, test_targets):
"""Format message for test run start."""
parts = []
parts.append("🧪 **Test Run Started**")
parts.append("")

info = []
info.append(f"**Build Preset:** `{build_preset}`")
info.append(f"**Test Size:** `{test_size}`")

if test_targets and test_targets != "ydb/":
info.append(f"**Test Targets:** `{test_targets}`")

parts.append("\n".join(info))
parts.append("")
parts.append("⏳ Tests are running...")

return "\n".join(parts)

def format_completion_message(build_preset, test_size, test_targets, summary_content, status):
"""Format message for test run completion."""
parts = []

# Status emoji
if status == "success":
parts.append("✅ **Test Run Completed Successfully**")
elif status == "failure":
parts.append("❌ **Test Run Failed**")
elif status == "cancelled":
parts.append("⚠️ **Test Run Cancelled**")
else:
parts.append("⚠️ **Test Run Completed**")

parts.append("")

info = []
info.append(f"**Build Preset:** `{build_preset}`")
info.append(f"**Test Size:** `{test_size}`")

if test_targets and test_targets != "ydb/":
info.append(f"**Test Targets:** `{test_targets}`")

parts.append("\n".join(info))
parts.append("")

# Add summary content if available
if summary_content and summary_content.strip():
parts.append("**Test Results:**")
parts.append("")
parts.append(summary_content.strip())

return "\n".join(parts)

if __name__ == "__main__":
if len(sys.argv) < 2:
print("::error::Usage: pr_comment.py <start|complete>")
sys.exit(1)

command = sys.argv[1]

if command not in ["start", "complete"]:
print(f"::error::Unknown command: {command}. Must be 'start' or 'complete'")
sys.exit(1)

pr_number = get_pr_number()

build_preset = os.environ.get("BUILD_PRESET")
if not build_preset:
raise ValueError("BUILD_PRESET environment variable is not set")

test_size = os.environ.get("TEST_SIZE")
if not test_size:
raise ValueError("TEST_SIZE environment variable is not set")

test_targets = os.environ.get("TEST_TARGETS", "ydb/")

workflow_run_url = get_workflow_run_url()

if command == "start":
message = format_start_message(build_preset, test_size, test_targets)
create_or_update_comment(pr_number, message, workflow_run_url)
else: # complete
status = os.environ.get("TEST_STATUS")
if not status:
raise ValueError("TEST_STATUS environment variable is not set")

# Read summary from summary_text.txt in workspace
workspace = os.environ.get("GITHUB_WORKSPACE")
if not workspace:
raise ValueError("GITHUB_WORKSPACE environment variable is not set")
summary_text_path = os.path.join(workspace, "summary_text.txt")

summary_content = ""
if os.path.exists(summary_text_path):
with open(summary_text_path, 'r', encoding='utf-8') as f:
summary_content = f.read()
if summary_content.strip():
print(f"::notice::Read {len(summary_content)} characters from {summary_text_path}")
else:
print(f"::warning::Summary file {summary_text_path} is empty")
else:
print(f"::warning::Summary file not found: {summary_text_path}")

message = format_completion_message(
build_preset, test_size, test_targets,
summary_content, status
)
create_or_update_comment(pr_number, message, workflow_run_url)

3 changes: 3 additions & 0 deletions .github/actions/validate_pr_description/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ runs:
env:
GITHUB_TOKEN: ${{ github.token }}
PR_BODY: ${{ inputs.pr_body}}
SHOW_RUN_TESTS_IN_PR: ${{ vars.SHOW_RUN_TESTS_IN_PR }}
SHOW_BACKPORT_IN_PR: ${{ vars.SHOW_BACKPORT_IN_PR }}
APP_DOMAIN: ${{ vars.APP_DOMAIN }}
run: |
python3 -m pip install PyGithub
echo "$PR_BODY" | python3 ${{ github.action_path }}/validate_pr_description.py
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/validate_pr_description/pr_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
r"https://github.com/ydb-platform/[a-z\-]+/issues/\d+",
r"https://st.yandex-team.ru/[a-zA-Z]+-\d+",
r"#\d+",
r"[a-zA-Z]+-\d+"
r"\b[a-zA-Z]+-\d+(?![-0-9])" # Negative lookahead to avoid matching branch names like "stable-25-2" in URLs
]

# Full PR template
Expand Down
Loading
Loading