8000 MAINT Adds script to create or update issue when CI fails (#21544) · scikit-learn/scikit-learn@2ee1305 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2ee1305

Browse files
authored
MAINT Adds script to create or update issue when CI fails (#21544)
1 parent e52c767 commit 2ee1305

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

azure-pipelines.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ jobs:
8181
# Tests that require large downloads over the networks are skipped in CI.
8282
# Here we make sure, that they are still run on a regular basis.
8383
SKLEARN_SKIP_NETWORK_TESTS: '0'
84+
CREATE_ISSUE_ON_TRACKER: 'true'
8485

8586
# Check compilation with intel C++ compiler (ICC)
8687
- template: build_tools/azure/posix.yml

build_tools/azure/posix.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
THREADPOOLCTL_VERSION: 'latest'
3434
COVERAGE: 'true'
3535
TEST_DOCSTRINGS: 'false'
36+
CREATE_ISSUE_ON_TRACKER: 'false'
3637
SHOW_SHORT_SUMMARY: 'false'
3738
strategy:
3839
matrix:
@@ -73,6 +74,30 @@ jobs:
7374
testRunTitle: ${{ format('{0}-$(Agent.JobName)', parameters.name) }}
7475
displayName: 'Publish Test Results'
7576
condition: succeededOrFailed()
77+
- task: UsePythonVersion@0
78+
inputs:
79+
versionSpec: '3.9'
80+
displayName: Place Python into path to update issue tracker
81+
condition: and(succeededOrFailed(), eq(variables['CREATE_ISSUE_ON_TRACKER'], 'true'),
82+
eq(variables['Build.Reason'], 'Schedule'))
83+
- bash: |
84+
set -ex
85+
if [[ $(BOT_GITHUB_TOKEN) == "" ]]; then
86+
echo "GitHub Token is not set. Issue tracker will not be updated."
87+
exit
88+
fi
89+
90+
LINK_TO_RUN="https://dev.azure.com/$BUILD_REPOSITORY_NAME/_build/results?buildId=$BUILD_BUILDID&view=logs&j=$SYSTEM_JOBID"
91+
CI_NAME="$SYSTEM_JOBIDENTIFIER"
92+
ISSUE_REPO="$BUILD_REPOSITORY_NAME"
93+
94+
pip install defusedxml PyGithub
95+
python maint_tools/create_issue_from_juint.py $(BOT_GITHUB_TOKEN) $CI_NAME $ISSUE_REPO $LINK_TO_RUN $JUNIT_FILE
96+
displayName: 'Update issue tracker'
97+
env:
98+
JUNIT_FILE: $(TEST_DIR)/$(JUNITXML)
99+
condition: and(succeededOrFailed(), eq(variables['CREATE_ISSUE_ON_TRACKER'], 'true'),
100+
eq(variables['Build.Reason'], 'Schedule'))
76101
- script: |
77102
build_tools/azure/upload_codecov.sh
78103
condition: and(succeeded(), eq(variables['COVERAGE'], 'true'))
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""Creates or updates an issue if the CI fails. This is useful to keep track of
2+
scheduled jobs that are failing repeatedly.
3+
4+
This script depends on:
5+
- `defusedxml` for safer parsing for xml
6+
- `PyGithub` for interacting with GitHub
7+
8+
The GitHub token only requires the `repo:public_repo` scope are described in
9+
https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes.
10+
This scope allows the bot to create and edit its own issues. It is best to use a
11+
github account that does **not** have commit access to the public repo.
12+
"""
13+
14+
from pathlib import Path
15+
import sys
16+
import argparse
17+
18+
import defusedxml.ElementTree as ET
19+
from github import Github
20+
21+
parser = argparse.ArgumentParser(
22+
description="Create or update issue from JUnit test results from pytest"
23+
)
24+
parser.add_argument(
25+
"bot_github_token", help="Github token for creating or updating an issue"
26+
)
27+
parser.add_argument("ci_name", help="Name of CI run instance")
28+
parser.add_argument("issue_repo", help="Repo to track issues")
29+
parser.add_argument("link_to_ci_run", help="URL to link to")
30+
parser.add_argument("junit_file", help="JUnit file")
31+
32+
args = parser.parse_args()
33+
gh = Github(args.bot_github_token)
34+
issue_repo = gh.get_repo(args.issue_repo)
35+
title = f"⚠️ CI failed on {args.ci_name} ⚠️"
36+
37+
38+
def get_issue():
39+
login = gh.get_user().login
40+
issues = gh.search_issues(
41+
f"repo:{args.issue_repo} {title} in:title state:open author:{login}"
42+
)
43+
first_page = issues.get_page(0)
44+
# Return issue if it exist
45+
return first_page[0] if first_page else None
46+
47+
48+
def create_or_update_issue(body):
49+
# Interact with GitHub API to create issue
50+
header = f"**CI Failed on [{args.ci_name}]({args.link_to_ci_run})**"
51+
body_text = f"{header}\n{body}"
52+
issue = get_issue()
53+
54+
if issue is None:
55+
# Create new issue
56+
issue = issue_repo.create_issue(title=title, body=body_text)
57+
print(f"Created issue in {args.issue_repo}#{issue.number}")
58+
sys.exit()
59+
else:
60+
# Update existing issue
61+
issue.edit(title=title, body=body_text)
62+
print(f"Updated issue in {args.issue_repo}#{issue.number}")
63+
sys.exit()
64+
65+
66+
junit_path = Path(args.junit_file)
67+
if not junit_path.exists():
68+
body = "Unable to find junit file. Please see link for details."
69+
create_or_update_issue(body)
70+
sys.exit()
71+
72+
# Find failures in junit file
73+
tree = ET.parse(args.junit_file)
74+
failure_cases = []
75+
76+
for item in tree.iter("testcase"):
77+
failure = item.find("failure")
78+
if failure is None:
79+
continue
80+
81+
failure_cases.append(
82+
{
83+
"title": item.attrib["name"],
84+
"body": failure.text,
85+
}
86+
)
87+
88+
if not failure_cases:
89+
print("Test has no failures!")
90+
issue = get_issue()
91+
if issue is not None:
92+
print(f"Closing issue #{issue.number}")
93+
new_body = (
94+
"## Closed issue because CI is no longer failing! ✅\n\n"
95+
f"[Successful run]({args.link_to_ci_run})\n\n"
96+
"## Previous failing issue\n\n"
97+
f"{issue.body}"
98+
)
99+
issue.edit(state="closed", body=new_body)
100+
sys.exit()
101+
102+
# Create content for issue
103+
issue_summary = (
104+
"<details><summary>{title}</summary>\n\n```python\n{body}\n```\n</details>\n"
105+
)
106+
body_list = [issue_summary.format(**case) for case in failure_cases]
107+
body = "\n".join(body_list)
108+
create_or_update_issue(body)

0 commit comments

Comments
 (0)
0