Files
wehub-resource-sync e768098d0e
Flake8 Lint / flake8 (push) Waiting to run
Spell check CI / Spell_Check (push) Waiting to run
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:52 +08:00

365 lines
12 KiB
Python

# Enforce the check of pipelines.
# This script will get the diff of the current branch and main branch, calculate the pipelines that should be triggered.
# Then it will check if the triggered pipelines are successful. This script will loop for 30*loop-times seconds at most.
# How many checks are triggered:
# 1. sdk checks: sdk_cli_tests, sdk_cli_azure_test, sdk_cli_global_config_tests are triggered.
# 2. examples checks: this script calculate the path filters and decide what should be triggered.
# Trigger checks and return the status of the checks:
# 1. If examples are not correctly generated, fail.
# 2. If required pipelines are not triggered within 6 rounds of loops, fail.
# 2.1 (special_care global variable could help on some pipelines that need to bypass the check)
# Check pipelines succeed or not:
# 1. These pipelines should return status within loop-times rounds.
# 2. If there is failed pipeline in the triggered pipelines, fail.
# Import necessary libraries
import os
import fnmatch
import subprocess
import time
import argparse
import json
import sys
# Define variables
github_repository = "microsoft/promptflow"
snippet_debug = os.getenv("SNIPPET_DEBUG", 0)
merge_commit = ""
loop_times = 40 # 40 * 30 seconds = 20 minutes
github_workspace = os.path.expanduser("~/promptflow/")
# Special cases for pipelines that need to be triggered more or less than default value 1.
# If 0, the pipeline will not be ignored in check enforcer.
# Please notice that the key should be the Job Name in the pipeline.
special_care = {
"sdk_cli_tests": 4,
"sdk_cli_azure_test_replay": 4,
"sdk_cli_global_config_tests": 1,
"tracing-e2e-test": 12,
"tracing-unit-test": 12,
"core_test": 4,
"azureml_serving_test": 4,
# "samples_connections_connection": 0,
}
# Copy from original yaml pipelines
checks = {
"sdk_cli_tests": [
"src/promptflow-core/**",
"src/promptflow-devkit/**",
"src/promptflow/**",
"src/promptflow-tracing/**",
"scripts/building/**",
"src/promptflow-recording/**",
],
"sdk_cli_global_config_tests": [
"src/promptflow-core/**",
"src/promptflow-devkit/**",
"src/promptflow-tracing/**",
"src/promptflow-azure/**",
"src/promptflow/**",
"scripts/building/**",
],
"sdk_cli_azure_test_replay": [
"src/promptflow/**",
"scripts/building/**",
"src/promptflow-tracing/**",
"src/promptflow-core/**",
"src/promptflow-devkit/**",
"src/promptflow-azure/**",
],
"tracing-e2e-test": [
"src/promptflow-tracing/**",
],
"tracing-unit-test": [
"src/promptflow-tracing/**",
],
"core_test": [
"src/promptflow-tracing/**",
"src/promptflow-core/**",
],
"azureml_serving_test": [
"src/promptflow-tracing/**",
"src/promptflow-core/**",
],
}
reverse_checks = {}
pipelines = {}
pipelines_count = {}
failed_reason = ""
# Define functions
def trigger_checks(valid_status_array):
global failed_reason
global github_repository
global merge_commit
global snippet_debug
global pipelines
global pipelines_count
output = subprocess.check_output(
f"gh api /repos/{github_repository}/commits/{merge_commit}/check-suites?per_page=100",
shell=True,
)
check_suites = json.loads(output)["check_suites"]
for suite in check_suites:
if snippet_debug != 0:
print(f"check-suites id {suite['id']}")
suite_id = suite["id"]
output = subprocess.check_output(
f"gh api /repos/{github_repository}/check-suites/{suite_id}/check-runs?per_page=100",
shell=True,
)
check_runs = json.loads(output)["check_runs"]
for run in check_runs:
if snippet_debug != 0:
print(f"check runs name {run['name']}")
for key in pipelines.keys():
value = pipelines[key]
if value == 0:
continue
if key in run["name"]:
pipelines_count[key] += 1
valid_status_array.append(run)
for key in pipelines.keys():
if pipelines_count[key] < pipelines[key]:
print(
f"[Failure]Pipeline {key} is triggered {pipelines_count[key]} times, less than {pipelines[key]} times."
)
failed_reason = "Not all pipelines are triggered."
else:
print(
f"Pipeline {key} is triggered {pipelines_count[key]} times, more or equal to {pipelines[key]} times."
)
def status_checks(valid_status_array):
global failed_reason
global pipelines
global pipelines_count
# Basic fact of sdk cli checked pipelines.
failed_reason = ""
# Loop through each valid status array.
for status in valid_status_array:
# Check if the pipeline was successful.
if status["conclusion"] and status["conclusion"].lower() == "success":
# Add 1 to the count of successful pipelines.
pass
# Check if the pipeline failed.
elif status["conclusion"] and status["conclusion"].lower() == "failure":
failed_reason = "Required pipelines are not successful."
# Check if the pipeline is still running.
else:
if failed_reason == "":
failed_reason = "Required pipelines are not finished."
# Print the status of the pipeline to the console.
print(status["name"] + " is checking.")
def trigger_prepare(input_paths):
global github_workspace
global checks
global reverse_checks
global pipelines
global pipelines_count
global failed_reason
global special_care
for input_path in input_paths:
if "samples_connections_connection" in input_path:
continue
if input_path.startswith("migration-guide/"):
continue
if "-maf" in input_path:
continue
# Check if the input path contains "examples" or "samples".
if "examples" in input_path or "samples" in input_path:
sys.path.append(os.path.expanduser(github_workspace + "/scripts/readme"))
from readme import main as readme_main
os.chdir(os.path.expanduser(github_workspace))
# Get the list of pipelines from the readme file.
pipelines_samples = readme_main(check=True)
git_diff_files = [
item
for item in subprocess.check_output(
["git", "diff", "--name-only", "HEAD"]
)
.decode("utf-8")
.split("\n")
if item != ""
]
for _ in git_diff_files:
failed_reason = "Run readme generation before check in"
return
# Merge the pipelines from the readme file with the original list of pipelines.
for key in pipelines_samples.keys():
value = pipelines_samples[key]
checks[key] = value
# Reverse checks.
for key in checks.keys():
value = checks[key]
for path in value:
if path in reverse_checks:
reverse_checks[path].append(key)
else:
reverse_checks[path] = [key]
# Render pipelines and pipelines_count using input_paths.
for input_path in input_paths:
# Input pattern /**: input_path should match in the middle.
# Input pattern /*: input_path should match last but one.
# Other input pattern: input_path should match last.
# Skip if input path is a markdown file.
if input_path.endswith(".md"):
continue
keys = [
key for key in reverse_checks.keys() if fnmatch.fnmatch(input_path, key)
]
# Loop through each key in the list of keys.
for key_item in keys:
# Loop through each pipeline in the list of pipelines.
for key in reverse_checks[key_item]:
# Check if the pipeline is in the list of pipelines.
if key in special_care:
pipelines[key] = special_care[key]
else:
pipelines[key] = 1
# Set the pipeline count to 0.
pipelines_count[key] = 0
def run_checks():
global github_repository
global snippet_debug
global merge_commit
global loop_times
global github_workspace
global failed_reason
if merge_commit == "":
merge_commit = (
subprocess.check_output(["git", "log", "-1"]).decode("utf-8").split("\n")
)
if snippet_debug != 0:
print(merge_commit)
for line in merge_commit:
if "Merge" in line and "into" in line:
merge_commit = line.split(" ")[-3]
break
if snippet_debug != 0:
print("MergeCommit " + merge_commit)
not_started_counter = 5
os.chdir(github_workspace)
# Get diff of current branch and main branch.
try:
git_merge_base = (
subprocess.check_output(["git", "merge-base", "origin/main", "HEAD"])
.decode("utf-8")
.rstrip()
)
git_diff = (
subprocess.check_output(
["git", "diff", "--name-only", "--diff-filter=d", f"{git_merge_base}"],
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.rstrip()
.split("\n")
)
for single_diff in git_diff:
print(single_diff)
except subprocess.CalledProcessError as e:
print("Exception on process, rc=", e.returncode, "output=", e.output)
raise e
# Prepare how many pipelines should be triggered.
trigger_prepare(git_diff)
if failed_reason != "":
raise Exception(failed_reason)
# Loop for 20 minutes at most.
for i in range(loop_times):
# Wait for 30 seconds.
time.sleep(30)
# Reset the failed reason.
failed_reason = ""
# Reset the valid status array.
valid_status_array = []
# Get all triggered pipelines.
# If not all pipelines are triggered, continue.
trigger_checks(valid_status_array)
if failed_reason != "":
if not_started_counter == 0:
raise Exception(failed_reason + " for 6 times.")
print(failed_reason)
not_started_counter -= 1
continue
# Get pipeline conclusion priority:
# 1. Not successful, Fail.
# 2. Not finished, Continue.
# 3. Successful, Break.
status_checks(valid_status_array)
# Check if the failed reason contains "not successful".
if "not successful" in failed_reason.lower():
raise Exception(failed_reason)
# Check if the failed reason contains "not finished".
elif "not finished" in failed_reason.lower():
print(failed_reason)
continue
# Otherwise, print that all required pipelines are successful.
else:
print("All required pipelines are successful.")
break
# Check if the failed reason is not empty.
if failed_reason != "":
raise Exception(failed_reason)
if __name__ == "__main__":
# Run the checks.
parser = argparse.ArgumentParser()
parser.add_argument(
"-m",
"--merge-commit",
help="merge commit sha",
)
parser.add_argument(
"-n",
"--loop-times",
type=int,
help="Loop times",
)
parser.add_argument(
"-t",
"--github-workspace",
help="base path of github workspace",
)
args = parser.parse_args()
if args.merge_commit:
merge_commit = args.merge_commit
if args.loop_times:
loop_times = args.loop_times
if args.github_workspace:
github_workspace = args.github_workspace
run_checks()