Some checks failed
Self-hosted runner (nightly-past-ci-caller) / Get number (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.11 (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.10 (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.9 (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.8 (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.7 (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.6 (push) Has been cancelled
Self-hosted runner (nightly-past-ci-caller) / TensorFlow 2.5 (push) Has been cancelled
Self-hosted runner (benchmark) / Benchmark (aws-g5-4xlarge-cache) (push) Has been cancelled
Build documentation / build (push) Has been cancelled
Build documentation / build_other_lang (push) Has been cancelled
CodeQL Security Analysis / CodeQL Analysis (push) Has been cancelled
New model PR merged notification / Notify new model (push) Has been cancelled
PR CI / pr-ci (push) Has been cancelled
Slow tests on important models (on Push - A10) / Get all modified files (push) Has been cancelled
Secret Leaks / trufflehog (push) Has been cancelled
Update Transformers metadata / build_and_package (push) Has been cancelled
Slow tests on important models (on Push - A10) / Model CI (push) Has been cancelled
Check Tiny Models / Check tiny models (push) Has been cancelled
Self-hosted runner (Intel Gaudi3 scheduled CI caller) / Model CI (push) Has been cancelled
Self-hosted runner (Intel Gaudi3 scheduled CI caller) / Pipeline CI (push) Has been cancelled
Self-hosted runner (Intel Gaudi3 scheduled CI caller) / Example CI (push) Has been cancelled
Self-hosted runner (Intel Gaudi3 scheduled CI caller) / DeepSpeed CI (push) Has been cancelled
Self-hosted runner (Intel Gaudi3 scheduled CI caller) / Trainer/FSDP CI (push) Has been cancelled
Nvidia CI - Flash Attn / Setup (push) Has been cancelled
Nvidia CI - Flash Attn / Model CI (push) Has been cancelled
Nvidia CI / Setup (push) Has been cancelled
Nvidia CI / Model CI (push) Has been cancelled
Nvidia CI / Torch pipeline CI (push) Has been cancelled
Nvidia CI / Example CI (push) Has been cancelled
Nvidia CI / Trainer/FSDP CI (push) Has been cancelled
Nvidia CI / DeepSpeed CI (push) Has been cancelled
Nvidia CI / Quantization CI (push) Has been cancelled
Nvidia CI / Kernels CI (push) Has been cancelled
Doctests / Setup (push) Has been cancelled
Doctests / Call doctest jobs (push) Has been cancelled
Doctests / Send results to webhook (push) Has been cancelled
Extras Smoke Test / Get supported Python versions (push) Has been cancelled
Extras Smoke Test / Test extras on Python ${{ matrix.python-version }} (push) Has been cancelled
Extras Smoke Test / Check Slack token availability (push) Has been cancelled
Extras Smoke Test / Notify failures to Slack (push) Has been cancelled
Self-hosted runner (AMD scheduled CI caller) / Trigger Scheduled AMD CI (push) Has been cancelled
Stale Bot / Close Stale Issues (push) Has been cancelled
264 lines
12 KiB
YAML
264 lines
12 KiB
YAML
name: CircleCI Failure Summary Comment
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, synchronize, reopened]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
comment:
|
|
runs-on: ubuntu-22.04
|
|
permissions:
|
|
pull-requests: write
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.13"
|
|
|
|
- name: Install dependencies
|
|
run: python -m pip install huggingface_hub
|
|
|
|
- name: Wait for CircleCI check suite completion
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
# Exit on error, undefined variables, or pipe failures
|
|
set -euo pipefail
|
|
|
|
echo "Waiting for CircleCI check suite to complete..."
|
|
# Timeout after 30 minutes (1800 seconds)
|
|
end=$((SECONDS + 1800))
|
|
|
|
while [ $SECONDS -lt $end ]; do
|
|
# Query GitHub API for check suites associated with this commit
|
|
# || echo "" allows retry on transient API failures instead of exiting
|
|
suite_json=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${COMMIT_SHA}/check-suites" \
|
|
--jq '.check_suites[] | select(.app.slug == "circleci-checks")' || echo "")
|
|
|
|
if [ -z "$suite_json" ]; then
|
|
echo "CircleCI check suite not found yet, retrying..."
|
|
else
|
|
status=$(echo "$suite_json" | jq -r '.status')
|
|
conclusion=$(echo "$suite_json" | jq -r '.conclusion // empty')
|
|
echo "CircleCI status: $status, conclusion: $conclusion"
|
|
|
|
# Check suite is done when status is "completed" AND conclusion is set
|
|
if [ "$status" = "completed" ] && [ -n "$conclusion" ]; then
|
|
echo "Check suite completed successfully"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# Poll every 20 seconds
|
|
sleep 20
|
|
done
|
|
|
|
echo "ERROR: Timed out waiting for CircleCI check suite"
|
|
exit 1
|
|
|
|
- name: Get CircleCI run's artifacts and upload them to Hub
|
|
id: circleci
|
|
env:
|
|
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
|
REPO: ${{ github.repository }}
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
# Step 1: Get CircleCI check suite ID
|
|
echo "Getting check suites for commit ${COMMIT_SHA}..."
|
|
check_suites=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
"https://api.github.com/repos/${REPO}/commits/${COMMIT_SHA}/check-suites")
|
|
|
|
circleci_suite_id=$(echo "$check_suites" | jq -r '.check_suites[] | select(.app.slug == "circleci-checks") | .id' | head -n 1)
|
|
echo "CircleCI check suite ID: ${circleci_suite_id}"
|
|
|
|
# Step 2: Get check runs from the CircleCI suite
|
|
echo "Getting check runs for suite ${circleci_suite_id}..."
|
|
check_runs=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \
|
|
"https://api.github.com/repos/${REPO}/check-suites/${circleci_suite_id}/check-runs")
|
|
|
|
# Step 3: Extract workflow ID from the "run_tests" check run
|
|
workflow_id=$(echo "$check_runs" | jq -r '.check_runs[] | select(.name == "run_tests") | .details_url' | grep -oP 'workflows/\K[a-f0-9-]+')
|
|
echo "CircleCI Workflow ID: ${workflow_id}"
|
|
|
|
# Step 4: Get all jobs in the workflow
|
|
echo "Getting jobs for workflow ${workflow_id}..."
|
|
jobs=$(curl -s \
|
|
"https://circleci.com/api/v2/workflow/${workflow_id}/job")
|
|
|
|
# Step 5: Extract collection_job details
|
|
# "first // empty": if collection_job is absent or has job_number=null, jq outputs nothing
|
|
# (empty string). Without this, jq -r would output the literal string "null", making
|
|
# [ -z "$collection_job_number" ] false and bypassing the early-exit check below.
|
|
collection_job_number=$(echo "$jobs" | jq -r '[.items[] | select(.name == "collection_job") | .job_number] | first // empty')
|
|
collection_job_id=$(echo "$jobs" | jq -r '[.items[] | select(.name == "collection_job") | .id] | first // empty')
|
|
echo "CircleCI Collection job number: ${collection_job_number}"
|
|
echo "CircleCI Collection job ID: ${collection_job_id}"
|
|
|
|
# When only the "empty" job ran (no tests selected), there is no collection_job.
|
|
# Exit gracefully to avoid curl hitting a broken URL.
|
|
if [ -z "$collection_job_number" ]; then
|
|
echo "No collection_job found (only empty job ran - no tests were selected). Skipping."
|
|
echo "artifact_found=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Step 6: Get artifacts list
|
|
echo "Getting artifacts for job ${collection_job_number}..."
|
|
artifacts=$(curl -s \
|
|
"https://circleci.com/api/v2/project/gh/${REPO}/${collection_job_number}/artifacts")
|
|
|
|
# Print for debugging; "|| true" prevents failure if the response is not valid JSON.
|
|
echo "$artifacts" | jq '.' || true
|
|
|
|
# Step 7: Download failure_summary.json specifically
|
|
# .items // [] : use empty array if .items is null (avoids "Cannot iterate over null")
|
|
# .[] : iterate over each artifact object in the array
|
|
# select(...) : keep only the artifact whose .path matches
|
|
# | .url : extract the download URL from the matched artifact
|
|
# first // empty: take the first match; outputs nothing (not "null") if no match found
|
|
failure_summary_url=$(echo "$artifacts" | jq -r '[.items // [] | .[] | select(.path == "outputs/failure_summary.json") | .url] | first // empty')
|
|
|
|
if [ -z "$failure_summary_url" ]; then
|
|
echo "failure_summary.json not found in artifacts - PR may not have latest main merged. Skipping."
|
|
echo "artifact_found=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
echo "Downloading failure_summary.json from: ${failure_summary_url}"
|
|
mkdir -p outputs
|
|
curl -s -L "${failure_summary_url}" -o outputs/failure_summary.json
|
|
ls -la outputs
|
|
|
|
echo "Downloaded failure_summary.json successfully"
|
|
|
|
# Verify the file was downloaded
|
|
if [ ! -f outputs/failure_summary.json ]; then
|
|
echo "Failed to download failure_summary.json - skipping."
|
|
echo "artifact_found=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
echo "File size: $(wc -c < outputs/failure_summary.json) bytes"
|
|
|
|
# Export variables for next steps
|
|
echo "artifact_found=true" >> $GITHUB_OUTPUT
|
|
echo "workflow_id=${workflow_id}" >> $GITHUB_OUTPUT
|
|
echo "collection_job_number=${collection_job_number}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Upload summaries to Hub
|
|
if: steps.circleci.outputs.artifact_found == 'true'
|
|
env:
|
|
HF_TOKEN: ${{ secrets.HF_CI_WRITE_TOKEN }}
|
|
CIRCLECI_RESULTS_DATASET_ID: "transformers-community/circleci-test-results"
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
python << 'EOF'
|
|
import os
|
|
from pathlib import Path
|
|
from huggingface_hub import HfApi
|
|
|
|
# Setup paths
|
|
pr_number = os.environ["PR_NUMBER"]
|
|
commit_short = os.environ["COMMIT_SHA"][:12]
|
|
folder_path = f"pr-{pr_number}/sha-{commit_short}"
|
|
|
|
# Create folder and move file
|
|
Path(folder_path).mkdir(parents=True, exist_ok=True)
|
|
Path("outputs/failure_summary.json").rename(f"{folder_path}/failure_summary.json")
|
|
|
|
# Upload to Hub
|
|
dataset_id = os.environ["CIRCLECI_RESULTS_DATASET_ID"]
|
|
api = HfApi(token=os.environ["HF_TOKEN"])
|
|
api.upload_folder(
|
|
commit_message=f"Update CircleCI artifacts for PR {pr_number} ({commit_short})",
|
|
folder_path=folder_path,
|
|
path_in_repo=folder_path,
|
|
repo_id=dataset_id,
|
|
repo_type="dataset",
|
|
)
|
|
|
|
print(f"Uploaded {folder_path} to {dataset_id}")
|
|
EOF
|
|
|
|
- name: Delete existing CircleCI summary comments
|
|
if: steps.circleci.outputs.artifact_found == 'true'
|
|
env:
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
|
with:
|
|
script: |
|
|
const PR_NUMBER = parseInt(process.env.PR_NUMBER, 10);
|
|
|
|
// Get all comments on the PR
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: PR_NUMBER
|
|
});
|
|
|
|
// Find existing bot comments that start with "View the CircleCI Test Summary for this PR:"
|
|
const existingComments = comments.filter(comment =>
|
|
comment.user.login === 'github-actions[bot]' &&
|
|
comment.body.startsWith('View the CircleCI Test Summary for this PR:')
|
|
);
|
|
|
|
// Delete all matching comments
|
|
for (const comment of existingComments) {
|
|
console.log(`Deleting comment #${comment.id}`);
|
|
await github.rest.issues.deleteComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: comment.id
|
|
});
|
|
}
|
|
|
|
console.log(`Deleted ${existingComments.length} old CircleCI summary comment(s)`);
|
|
|
|
- name: Post comment with helper link
|
|
if: steps.circleci.outputs.artifact_found == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
COMMIT_SHORT="${PR_SHA:0:12}"
|
|
SUMMARY_FILE="pr-${PR_NUMBER}/sha-${COMMIT_SHORT}/failure_summary.json"
|
|
|
|
if [ ! -f "$SUMMARY_FILE" ]; then
|
|
echo "failure_summary.json missing, skipping comment."
|
|
exit 0
|
|
fi
|
|
|
|
failures=$(jq '.failures | length' "$SUMMARY_FILE")
|
|
if [ "$failures" -eq 0 ]; then
|
|
echo "No failures detected, skipping PR comment."
|
|
exit 0
|
|
fi
|
|
|
|
# Build Space URL with encoded parameters
|
|
repo_enc=$(jq -rn --arg v "$GITHUB_REPOSITORY" '$v|@uri')
|
|
pr_enc=$(jq -rn --arg v "$PR_NUMBER" '$v|@uri')
|
|
sha_short="${PR_SHA:0:6}"
|
|
sha_enc=$(jq -rn --arg v "$sha_short" '$v|@uri')
|
|
SPACE_URL="https://huggingface.co/spaces/transformers-community/circle-ci-viz?pr=${pr_enc}&sha=${sha_enc}"
|
|
|
|
# Post comment (using printf for proper newlines)
|
|
gh api \
|
|
--method POST \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
"repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
|
|
-f body="$(printf "View the CircleCI Test Summary for this PR:\n\n%s" "$SPACE_URL")"
|