Skip to content

Commit dbfc674

Browse files
committed
small updates
1 parent 03562e6 commit dbfc674

File tree

3 files changed

+171
-3
lines changed

3 files changed

+171
-3
lines changed

.github/workflows/circleci-failure-summary-comment.yml

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
python-version: "3.13"
2727

2828
- name: Install dependencies
29-
run: python -m pip install requests
29+
run: python -m pip install requests huggingface_hub
3030

3131
- name: Wait for CircleCI check suite completion
3232
env:
@@ -69,12 +69,20 @@ jobs:
6969
run: |
7070
python utils/process_circleci_workflow_test_reports.py --workflow_id "${{ steps.circleci.outputs.workflow_id }}"
7171
72-
- name: Post comment with failure summary
72+
- name: Upload summaries to Hub
73+
env:
74+
HF_TOKEN: ${{ secrets.TRANSFORMERS_HUB_BOT_HF_TOKEN }}
75+
CIRCLECI_RESULTS_DATASET_ID: transformers-community/circleci-test-results
76+
run: |
77+
python utils/upload_circleci_results.py --source-dir outputs --dataset-id "${CIRCLECI_RESULTS_DATASET_ID}"
78+
79+
- name: Post comment with helper link
7380
env:
7481
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7582
GITHUB_RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
7683
github_repository: ${{ github.repository }}
7784
pr_number: ${{ github.event.pull_request.number }}
85+
pr_sha: ${{ github.event.pull_request.head.sha }}
7886
run: |
7987
if [ ! -f outputs/failure_summary.json ]; then
8088
echo "failure_summary.json missing, skipping comment."
@@ -85,7 +93,26 @@ jobs:
8593
echo "No failures detected, skipping PR comment."
8694
exit 0
8795
fi
88-
body="$(cat outputs/failure_summary.md)"
96+
SPACE_SLUG="transformers-community/circleci-test-collection-helper"
97+
SPACE_BASE="https://huggingface.co/spaces/${SPACE_SLUG}"
98+
QUERY=$(python - <<'PY'
99+
import os
100+
from urllib.parse import urlencode
101+
102+
params = {
103+
"repo": os.environ.get("github_repository"),
104+
"pr": os.environ.get("pr_number"),
105+
"sha": os.environ.get("pr_sha"),
106+
}
107+
print(urlencode({k: v for k, v in params.items() if v}))
108+
PY
109+
)
110+
if [ -n "$QUERY" ]; then
111+
SPACE_URL="${SPACE_BASE}?${QUERY}"
112+
else
113+
SPACE_URL="${SPACE_BASE}"
114+
fi
115+
body="View the CircleCI test collection helper for this PR:\n\n${SPACE_URL}"
89116
gh api \
90117
--method POST \
91118
-H "Accept: application/vnd.github+json" \

utils/process_circleci_workflow_test_reports.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import re
2020
from collections import Counter
21+
from datetime import datetime, timezone
2122
from typing import Callable
2223

2324
import requests
@@ -147,6 +148,38 @@ def _normalize_test_nodeid(nodeid: str) -> str:
147148
return normalized
148149

149150

151+
def _collect_metadata(workflow_id: str) -> dict[str, str | None]:
152+
repo_owner = os.environ.get("CIRCLE_PROJECT_USERNAME")
153+
repo_name = os.environ.get("CIRCLE_PROJECT_REPONAME")
154+
repo_slug = "/".join(part for part in [repo_owner, repo_name] if part)
155+
commit_sha = os.environ.get("CIRCLE_SHA1")
156+
branch = os.environ.get("CIRCLE_BRANCH")
157+
pull_request = os.environ.get("CIRCLE_PULL_REQUEST")
158+
pr_number = os.environ.get("CIRCLE_PR_NUMBER")
159+
if not pr_number and pull_request and "/" in pull_request:
160+
pr_number = pull_request.rsplit("/", 1)[-1]
161+
build_num = os.environ.get("CIRCLE_BUILD_NUM")
162+
timestamp = os.environ.get("CIRCLE_WORKFLOW_CREATED_AT")
163+
if not timestamp:
164+
timestamp = datetime.now(timezone.utc).isoformat()
165+
commit_short = (commit_sha or "unknown")[:8]
166+
dataset_subfolder = f"{repo_slug.replace('/', '__') or 'unknown_repo'}/pr-{pr_number or 'none'}/sha-{commit_short}/workflow-{workflow_id}"
167+
metadata = {
168+
"workflow_id": workflow_id,
169+
"repo_owner": repo_owner,
170+
"repo_name": repo_name,
171+
"repository": repo_slug,
172+
"branch": branch,
173+
"commit_sha": commit_sha,
174+
"pull_request": pull_request,
175+
"pull_request_number": pr_number,
176+
"build_number": build_num,
177+
"collected_at": timestamp,
178+
"dataset_subfolder": dataset_subfolder,
179+
}
180+
return metadata
181+
182+
150183
def process_circleci_workflow(
151184
workflow_id: str,
152185
output_dir: str = "outputs",
@@ -287,6 +320,20 @@ def process_circleci_workflow(
287320
with open(os.path.join(output_dir, "failure_summary.md"), "w") as fp:
288321
fp.write(markdown_text)
289322

323+
metadata = _collect_metadata(workflow_id)
324+
aggregate_payload = {
325+
"metadata": metadata,
326+
"jobs": workflow_summary,
327+
"tests": new_workflow_summary,
328+
"failures": failure_entries,
329+
"failures_by_test": failures_by_test,
330+
"failures_by_model": failures_by_model,
331+
}
332+
with open(os.path.join(output_dir, "collection_summary.json"), "w") as fp:
333+
json.dump(aggregate_payload, fp, indent=4)
334+
with open(os.path.join(output_dir, "metadata.json"), "w") as fp:
335+
json.dump(metadata, fp, indent=4)
336+
290337

291338
def main():
292339
parser = argparse.ArgumentParser()

utils/upload_circleci_results.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/usr/bin/env python
2+
# Copyright 2025 The HuggingFace Inc. team.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
import argparse
16+
import json
17+
import os
18+
from pathlib import Path
19+
20+
from huggingface_hub import CommitOperationAdd, HfApi
21+
22+
DEFAULT_DATASET_ID = os.environ.get("CIRCLECI_RESULTS_DATASET_ID", "transformers-community/circleci-test-results")
23+
24+
25+
def parse_args() -> argparse.Namespace:
26+
parser = argparse.ArgumentParser(description="Upload CircleCI collection outputs to the Hub.")
27+
parser.add_argument("--source-dir", type=str, default="outputs", help="Directory containing summary files.")
28+
parser.add_argument("--dataset-id", type=str, default=DEFAULT_DATASET_ID, help="Target dataset ID to update.")
29+
return parser.parse_args()
30+
31+
32+
def _load_metadata(source_dir: Path) -> dict:
33+
metadata_path = source_dir / "metadata.json"
34+
if not metadata_path.exists():
35+
raise FileNotFoundError(f"metadata.json missing in {source_dir}")
36+
with metadata_path.open() as fp:
37+
return json.load(fp)
38+
39+
40+
def _collect_files(source_dir: Path, base_dir: str) -> list[CommitOperationAdd]:
41+
filenames = [
42+
"collection_summary.json",
43+
"failure_summary.json",
44+
"failure_summary.md",
45+
"test_summary.json",
46+
"metadata.json",
47+
]
48+
operations = []
49+
for filename in filenames:
50+
path = source_dir / filename
51+
if not path.exists():
52+
continue
53+
remote = f"{base_dir}/{filename}"
54+
operations.append(CommitOperationAdd(path_in_repo=remote, path_or_fileobj=str(path)))
55+
return operations
56+
57+
58+
def main():
59+
args = parse_args()
60+
source_dir = Path(args.source_dir).resolve()
61+
dataset_id = args.dataset_id
62+
if not dataset_id:
63+
raise ValueError("Dataset ID is required.")
64+
65+
token = os.environ.get("HF_TOKEN") or os.environ.get("TRANSFORMERS_HUB_BOT_HF_TOKEN")
66+
if not token:
67+
raise RuntimeError("HF token not provided. Set HF_TOKEN or TRANSFORMERS_HUB_BOT_HF_TOKEN.")
68+
69+
metadata = _load_metadata(source_dir)
70+
pr_number = metadata.get("pull_request_number") or "none"
71+
commit_sha = metadata.get("commit_sha") or "unknown"
72+
commit_short = commit_sha[:12]
73+
base_dir = f"pr-{pr_number}/sha-{commit_short}"
74+
75+
operations = _collect_files(source_dir, base_dir)
76+
if not operations:
77+
raise RuntimeError(f"No summary files found in {source_dir}.")
78+
79+
api = HfApi(token=token)
80+
api.create_repo(repo_id=dataset_id, repo_type="dataset", exist_ok=True, token=token)
81+
82+
commit_message = f"Update CircleCI artifacts for PR {pr_number} ({commit_short})"
83+
api.create_commit(
84+
repo_id=dataset_id,
85+
repo_type="dataset",
86+
operations=operations,
87+
commit_message=commit_message,
88+
token=token,
89+
)
90+
print(f"Uploaded {len(operations)} files to {dataset_id}:{base_dir}")
91+
92+
93+
if __name__ == "__main__":
94+
main()

0 commit comments

Comments
 (0)