ClickHouse/tests/ci/run_check.py

270 lines
8.6 KiB
Python
Raw Normal View History

2021-09-15 12:59:39 +00:00
#!/usr/bin/env python3
import sys
2021-09-15 13:04:29 +00:00
import logging
2022-01-13 11:08:31 +00:00
import re
from typing import Tuple
2021-11-26 14:00:09 +00:00
from github import Github
from commit_status_helper import (
get_commit,
post_labels,
remove_labels,
reset_mergeable_check,
)
from env_helper import GITHUB_RUN_URL, GITHUB_REPOSITORY, GITHUB_SERVER_URL
from get_robot_token import get_best_robot_token
2022-04-21 14:33:46 +00:00
from pr_info import FORCE_TESTS_LABEL, PRInfo
from workflow_approve_rerun_lambda.app import TRUSTED_CONTRIBUTORS
2021-09-15 12:59:39 +00:00
NAME = "Run Check"
2021-09-15 12:59:39 +00:00
TRUSTED_ORG_IDS = {
54801242, # clickhouse
}
2022-02-05 17:26:43 +00:00
OK_SKIP_LABELS = {"release", "pr-backport", "pr-cherrypick"}
CAN_BE_TESTED_LABEL = "can be tested"
2021-09-15 12:59:39 +00:00
DO_NOT_TEST_LABEL = "do not test"
2022-03-29 17:28:18 +00:00
SUBMODULE_CHANGED_LABEL = "submodule changed"
2021-09-15 12:59:39 +00:00
# They are used in .github/PULL_REQUEST_TEMPLATE.md, keep comments there
# updated accordingly
2022-04-19 12:47:18 +00:00
LABELS = {
"pr-backward-incompatible": ["Backward Incompatible Change"],
"pr-bugfix": [
"Bug Fix",
"Bug Fix (user-visible misbehaviour in official stable or prestable release)",
"Bug Fix (user-visible misbehavior in official stable or prestable release)",
2022-04-19 12:47:18 +00:00
],
"pr-build": [
"Build/Testing/Packaging Improvement",
"Build Improvement",
"Build/Testing Improvement",
"Build",
"Packaging Improvement",
],
"pr-documentation": [
"Documentation (changelog entry is not required)",
"Documentation",
],
"pr-feature": ["New Feature"],
"pr-improvement": ["Improvement"],
"pr-not-for-changelog": [
"Not for changelog (changelog entry is not required)",
"Not for changelog",
],
"pr-performance": ["Performance Improvement"],
2022-03-29 13:48:57 +00:00
}
2022-04-19 12:47:18 +00:00
CATEGORY_TO_LABEL = {c: lb for lb, categories in LABELS.items() for c in categories}
2021-09-15 12:59:39 +00:00
def pr_is_by_trusted_user(pr_user_login, pr_user_orgs):
if pr_user_login.lower() in TRUSTED_CONTRIBUTORS:
logging.info("User '%s' is trusted", pr_user_login)
2021-09-15 12:59:39 +00:00
return True
logging.info("User '%s' is not trusted", pr_user_login)
2021-09-15 12:59:39 +00:00
for org_id in pr_user_orgs:
if org_id in TRUSTED_ORG_IDS:
2022-01-13 11:06:50 +00:00
logging.info(
"Org '%s' is trusted; will mark user %s as trusted",
org_id,
pr_user_login,
)
2021-09-15 12:59:39 +00:00
return True
logging.info("Org '%s' is not trusted", org_id)
2021-09-15 12:59:39 +00:00
return False
2022-01-13 11:06:50 +00:00
2021-09-15 12:59:39 +00:00
# Returns whether we should look into individual checks for this PR. If not, it
# can be skipped entirely.
# Returns can_run, description, labels_state
def should_run_checks_for_pr(pr_info: PRInfo) -> Tuple[bool, str, str]:
2021-09-15 12:59:39 +00:00
# Consider the labels and whether the user is trusted.
2021-12-22 08:13:04 +00:00
print("Got labels", pr_info.labels)
if FORCE_TESTS_LABEL in pr_info.labels:
print(f"Label '{FORCE_TESTS_LABEL}' set, forcing remaining checks")
return True, f"Labeled '{FORCE_TESTS_LABEL}'", "pending"
2021-09-15 12:59:39 +00:00
if DO_NOT_TEST_LABEL in pr_info.labels:
print(f"Label '{DO_NOT_TEST_LABEL}' set, skipping remaining checks")
return False, f"Labeled '{DO_NOT_TEST_LABEL}'", "success"
2021-09-15 12:59:39 +00:00
if CAN_BE_TESTED_LABEL not in pr_info.labels and not pr_is_by_trusted_user(
2022-01-13 11:06:50 +00:00
pr_info.user_login, pr_info.user_orgs
):
2022-09-07 08:51:02 +00:00
print(
f"PRs by untrusted users need the '{CAN_BE_TESTED_LABEL}' label - please contact a member of the core team"
)
return False, "Needs 'can be tested' label", "failure"
2021-09-15 12:59:39 +00:00
if OK_SKIP_LABELS.intersection(pr_info.labels):
return (
False,
"Don't try new checks for release/backports/cherry-picks",
"success",
)
2021-09-30 11:26:46 +00:00
return True, "No special conditions apply", "pending"
2021-09-15 12:59:39 +00:00
2021-11-26 14:00:09 +00:00
2022-11-10 16:11:23 +00:00
def check_pr_description(pr_info: PRInfo) -> Tuple[str, str]:
2022-01-13 13:51:48 +00:00
lines = list(
map(lambda x: x.strip(), pr_info.body.split("\n") if pr_info.body else [])
2022-01-13 13:51:48 +00:00
)
lines = [re.sub(r"\s+", " ", line) for line in lines]
2022-01-13 11:08:31 +00:00
# Check if body contains "Reverts ClickHouse/ClickHouse#36337"
if [
True
for line in lines
if re.match(rf"\AReverts {GITHUB_REPOSITORY}#[\d]+\Z", line)
]:
return "", LABELS["pr-not-for-changelog"][0]
2022-01-13 11:08:31 +00:00
category = ""
entry = ""
i = 0
while i < len(lines):
2022-04-04 22:52:37 +00:00
if re.match(r"(?i)^[#>*_ ]*change\s*log\s*category", lines[i]):
2022-01-13 11:08:31 +00:00
i += 1
if i >= len(lines):
break
# Can have one empty line between header and the category
# itself. Filter it out.
if not lines[i]:
i += 1
if i >= len(lines):
break
category = re.sub(r"^[-*\s]*", "", lines[i])
i += 1
# Should not have more than one category. Require empty line
# after the first found category.
if i >= len(lines):
break
if lines[i]:
second_category = re.sub(r"^[-*\s]*", "", lines[i])
result_status = (
"More than one changelog category specified: '"
+ category
+ "', '"
+ second_category
+ "'"
)
2022-03-29 13:48:57 +00:00
return result_status[:140], category
2022-01-13 11:08:31 +00:00
elif re.match(
2022-04-04 22:52:37 +00:00
r"(?i)^[#>*_ ]*(short\s*description|change\s*log\s*entry)", lines[i]
2022-01-13 11:08:31 +00:00
):
i += 1
# Can have one empty line between header and the entry itself.
# Filter it out.
if i < len(lines) and not lines[i]:
i += 1
# All following lines until empty one are the changelog entry.
entry_lines = []
while i < len(lines) and lines[i]:
entry_lines.append(lines[i])
i += 1
entry = " ".join(entry_lines)
# Don't accept changelog entries like '...'.
entry = re.sub(r"[#>*_.\- ]", "", entry)
else:
i += 1
if not category:
2022-03-29 13:48:57 +00:00
return "Changelog category is empty", category
2022-01-13 11:08:31 +00:00
# Filter out the PR categories that are not for changelog.
if re.match(
r"(?i)doc|((non|in|not|un)[-\s]*significant)|(not[ ]*for[ ]*changelog)",
category,
):
2022-03-29 13:48:57 +00:00
return "", category
2022-01-13 11:08:31 +00:00
if not entry:
2022-03-29 13:48:57 +00:00
return f"Changelog entry required for category '{category}'", category
2022-01-13 11:08:31 +00:00
2022-03-29 13:48:57 +00:00
return "", category
2022-01-13 11:08:31 +00:00
2021-09-15 12:59:39 +00:00
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
2022-03-29 17:28:18 +00:00
pr_info = PRInfo(need_orgs=True, pr_event_from_api=True, need_changed_files=True)
can_run, description, labels_state = should_run_checks_for_pr(pr_info)
gh = Github(get_best_robot_token(), per_page=100)
commit = get_commit(gh, pr_info.sha)
2022-01-13 11:08:31 +00:00
2022-04-19 12:47:18 +00:00
description_error, category = check_pr_description(pr_info)
2022-03-30 08:58:34 +00:00
pr_labels_to_add = []
pr_labels_to_remove = []
2022-03-29 17:50:06 +00:00
if (
2022-04-19 12:47:18 +00:00
category in CATEGORY_TO_LABEL
and CATEGORY_TO_LABEL[category] not in pr_info.labels
2022-03-29 17:50:06 +00:00
):
2022-04-19 12:47:18 +00:00
pr_labels_to_add.append(CATEGORY_TO_LABEL[category])
2022-03-30 08:58:34 +00:00
for label in pr_info.labels:
2022-03-30 09:19:11 +00:00
if (
2022-04-19 12:47:18 +00:00
label in CATEGORY_TO_LABEL.values()
and category in CATEGORY_TO_LABEL
and label != CATEGORY_TO_LABEL[category]
2022-03-30 09:19:11 +00:00
):
2022-03-30 08:58:34 +00:00
pr_labels_to_remove.append(label)
2022-03-29 13:48:57 +00:00
2022-03-29 17:28:18 +00:00
if pr_info.has_changes_in_submodules():
2022-03-30 08:58:34 +00:00
pr_labels_to_add.append(SUBMODULE_CHANGED_LABEL)
2022-03-29 17:50:06 +00:00
elif SUBMODULE_CHANGED_LABEL in pr_info.labels:
2022-03-30 08:58:34 +00:00
pr_labels_to_remove.append(SUBMODULE_CHANGED_LABEL)
print(f"Change labels: add {pr_labels_to_add}, remove {pr_labels_to_remove}")
2022-03-30 08:58:34 +00:00
if pr_labels_to_add:
post_labels(gh, pr_info, pr_labels_to_add)
if pr_labels_to_remove:
remove_labels(gh, pr_info, pr_labels_to_remove)
2022-03-29 17:28:18 +00:00
reset_mergeable_check(commit, "skipped")
2022-07-06 03:37:01 +00:00
2022-04-19 12:47:18 +00:00
if description_error:
2022-04-04 23:06:46 +00:00
print(
"::error ::Cannot run, PR description does not match the template: "
2022-04-19 12:47:18 +00:00
f"{description_error}"
2022-04-04 23:06:46 +00:00
)
2022-01-26 12:23:20 +00:00
logging.info(
2022-04-04 23:06:46 +00:00
"PR body doesn't match the template: (start)\n%s\n(end)\n" "Reason: %s",
pr_info.body,
2022-04-19 12:47:18 +00:00
description_error,
2022-01-26 12:23:20 +00:00
)
2022-01-13 11:08:31 +00:00
url = (
f"{GITHUB_SERVER_URL}/{GITHUB_REPOSITORY}/"
"blob/master/.github/PULL_REQUEST_TEMPLATE.md?plain=1"
)
commit.create_status(
context=NAME,
2022-04-19 12:47:18 +00:00
description=description_error[:139],
2022-01-13 11:08:31 +00:00
state="failure",
target_url=url,
)
sys.exit(1)
url = GITHUB_RUN_URL
2021-09-15 12:59:39 +00:00
if not can_run:
2021-09-16 10:39:36 +00:00
print("::notice ::Cannot run")
2022-01-13 11:06:50 +00:00
commit.create_status(
context=NAME, description=description, state=labels_state, target_url=url
2022-01-13 11:06:50 +00:00
)
2021-09-15 12:59:39 +00:00
sys.exit(1)
2021-09-15 13:36:48 +00:00
else:
2022-05-24 10:56:57 +00:00
print("::notice ::Can run")
commit.create_status(
context=NAME, description=description, state="pending", target_url=url
)