aboutsummaryrefslogtreecommitdiff
path: root/.ci/premerge_advisor_explain.py
diff options
context:
space:
mode:
Diffstat (limited to '.ci/premerge_advisor_explain.py')
-rw-r--r--.ci/premerge_advisor_explain.py117
1 files changed, 107 insertions, 10 deletions
diff --git a/.ci/premerge_advisor_explain.py b/.ci/premerge_advisor_explain.py
index 06c6cb9..6296599 100644
--- a/.ci/premerge_advisor_explain.py
+++ b/.ci/premerge_advisor_explain.py
@@ -4,20 +4,81 @@
"""Script for getting explanations from the premerge advisor."""
import argparse
-import os
import platform
import sys
+import json
+
+# TODO(boomanaiden154): Remove the optional call once we can require Python
+# 3.10.
+from typing import Optional
import requests
+import github
+import github.PullRequest
import generate_test_report_lib
PREMERGE_ADVISOR_URL = (
"http://premerge-advisor.premerge-advisor.svc.cluster.local:5000/explain"
)
+COMMENT_TAG = "<!--PREMERGE ADVISOR COMMENT: {platform}-->"
+
+
+def get_comment_id(platform: str, pr: github.PullRequest.PullRequest) -> Optional[int]:
+ platform_comment_tag = COMMENT_TAG.format(platform=platform)
+ for comment in pr.as_issue().get_comments():
+ if platform_comment_tag in comment.body:
+ return comment.id
+ return None
+
+
+def get_comment(
+ github_token: str,
+ pr_number: int,
+ body: str,
+) -> dict[str, str]:
+ repo = github.Github(github_token).get_repo("llvm/llvm-project")
+ pr = repo.get_issue(pr_number).as_pull_request()
+ body = COMMENT_TAG.format(platform=platform.system()) + "\n" + body
+ comment = {"body": body}
+ comment_id = get_comment_id(platform.system(), pr)
+ if comment_id:
+ comment["id"] = comment_id
+ return comment
-def main(commit_sha: str, build_log_files: list[str]):
+def main(
+ commit_sha: str,
+ build_log_files: list[str],
+ github_token: str,
+ pr_number: int,
+ return_code: int,
+):
+ """The main entrypoint for the script.
+
+ This function parses failures from files, requests information from the
+ premerge advisor, and may write a Github comment depending upon the output.
+ There are four different scenarios:
+ 1. There has never been a previous failure and the job passes - We do not
+ create a comment. We write out an empty file to the comment path so the
+ issue-write workflow knows not to create anything.
+ 2. There has never been a previous failure and the job fails - We create a
+ new comment containing the failure information and any possible premerge
+ advisor findings.
+ 3. There has been a previous failure and the job passes - We update the
+ existing comment by passing its ID and a passed message to the
+ issue-write workflow.
+ 4. There has been a previous failure and the job fails - We update the
+ existing comment in the same manner as above, but generate the comment
+ as if we have a failure.
+
+ Args:
+ commit_sha: The base commit SHA for this PR run.
+ build_log_files: The list of JUnit XML files and ninja logs.
+ github_token: The token to use to access the Github API.
+ pr_number: The number of the PR associated with this run.
+ return_code: The numerical return code of ninja/CMake.
+ """
junit_objects, ninja_logs = generate_test_report_lib.load_info_from_files(
build_log_files
)
@@ -34,22 +95,52 @@ def main(commit_sha: str, build_log_files: list[str]):
explanation_request["failures"].append(
{"name": name, "message": failure_messsage}
)
- else:
+ elif return_code != 0:
ninja_failures = generate_test_report_lib.find_failure_in_ninja_logs(ninja_logs)
for name, failure_message in ninja_failures:
explanation_request["failures"].append(
{"name": name, "message": failure_message}
)
- advisor_response = requests.get(PREMERGE_ADVISOR_URL, json=explanation_request)
- if advisor_response.status_code == 200:
- print(advisor_response.json())
- else:
- print(advisor_response.reason)
+ comments = []
+ advisor_explanations = []
+ if return_code != 0:
+ advisor_response = requests.get(
+ PREMERGE_ADVISOR_URL, json=explanation_request, timeout=5
+ )
+ if advisor_response.status_code == 200:
+ print(advisor_response.json())
+ advisor_explanations = advisor_response.json()
+ else:
+ print(advisor_response.reason)
+ comments.append(
+ get_comment(
+ github_token,
+ pr_number,
+ generate_test_report_lib.generate_report(
+ generate_test_report_lib.compute_platform_title(),
+ return_code,
+ junit_objects,
+ ninja_logs,
+ failure_explanations_list=advisor_explanations,
+ ),
+ )
+ )
+ if return_code == 0 and "id" not in comments[0]:
+ # If the job succeeds and there is not an existing comment, we
+ # should not write one to reduce noise.
+ comments = []
+ comments_file_name = f"comments-{platform.system()}-{platform.machine()}"
+ with open(comments_file_name, "w") as comment_file_handle:
+ json.dump(comments, comment_file_handle)
+ print(f"Wrote comments to {comments_file_name}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("commit_sha", help="The base commit SHA for the test.")
+ parser.add_argument("return_code", help="The build's return code", type=int)
+ parser.add_argument("github_token", help="Github authentication token", type=str)
+ parser.add_argument("pr_number", help="The PR number", type=int)
parser.add_argument(
"build_log_files", help="Paths to JUnit report files and ninja logs.", nargs="*"
)
@@ -57,7 +148,13 @@ if __name__ == "__main__":
# Skip looking for results on AArch64 for now because the premerge advisor
# service is not available on AWS currently.
- if platform.machine() == "arm64":
+ if platform.machine() == "arm64" or platform.machine() == "aarch64":
sys.exit(0)
- main(args.commit_sha, args.build_log_files)
+ main(
+ args.commit_sha,
+ args.build_log_files,
+ args.github_token,
+ args.pr_number,
+ args.return_code,
+ )