Skip to content

Commit f236968

Browse files
committed
CI: add failure reporter (workflow_run) scoped to jbrinkman/matrix-errors; aggregates artifacts and updates rolling issue
Signed-off-by: jbrinkman <[email protected]>
1 parent 380cd28 commit f236968

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: C# failure reporter
2+
3+
on:
4+
workflow_run:
5+
workflows: ["C# tests"]
6+
types: [completed]
7+
branches: [jbrinkman/matrix-errors]
8+
9+
permissions:
10+
actions: read
11+
contents: read
12+
issues: write
13+
14+
jobs:
15+
report:
16+
# Only handle scheduled runs or manual full-matrix runs ("C# Matrix Tests") that ended in failure
17+
if: github.event.workflow_run.conclusion == 'failure' && (github.event.workflow_run.event == 'schedule' || github.event.workflow_run.display_title == 'C# Matrix Tests')
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Download artifacts from triggering run
21+
uses: dawidd6/action-download-artifact@v6
22+
with:
23+
run_id: ${{ github.event.workflow_run.id }}
24+
path: downloaded
25+
26+
- name: Aggregate failures
27+
id: aggregate
28+
shell: bash
29+
run: |
30+
set -euo pipefail
31+
if ! command -v jq >/dev/null 2>&1; then
32+
sudo apt-get update -y >/dev/null 2>&1 || true
33+
sudo apt-get install -y jq >/dev/null 2>&1 || true
34+
fi
35+
shopt -s globstar nullglob
36+
total_failed=0
37+
total_suites=0
38+
md='<!-- run:${{ github.event.workflow_run.id }} -->\n'
39+
md+="# Nightly CI failures for ${{ github.repository }}\n\n"
40+
md+="Triggered: ${{ github.event.workflow_run.run_started_at }} • [Run link](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }})\n\n"
41+
found_any=0
42+
for f in downloaded/**/failures.json; do
43+
found_any=1
44+
failed=$(jq -r '.summary.failed // 0' "$f" 2>/dev/null || echo 0)
45+
total=$(jq -r '.summary.total // 0' "$f" 2>/dev/null || echo 0)
46+
job=$(jq -r '.jobName // empty' "$f" 2>/dev/null || echo "")
47+
[ -z "$job" ] && job=$(basename "$(dirname "$f")")
48+
runUrl=$(jq -r '.links.runUrl // empty' "$f" 2>/dev/null || echo "")
49+
total_failed=$((total_failed+failed))
50+
total_suites=$((total_suites+1))
51+
md+="- ${job}: ${failed}/${total} failed"
52+
if [ -n "$runUrl" ]; then
53+
md+=" — [run](${runUrl})"
54+
fi
55+
md+="\n"
56+
done
57+
if [ $found_any -eq 0 ]; then
58+
md+="No failures.json artifacts were found to summarize.\n"
59+
else
60+
md+="\nTotal failed tests across ${total_suites} matrix jobs: ${total_failed}\n"
61+
fi
62+
printf "%b" "$md" > summary.md
63+
echo "summary_path=$(pwd)/summary.md" >> "$GITHUB_OUTPUT"
64+
echo "total_failed=${total_failed}" >> "$GITHUB_OUTPUT"
65+
66+
- name: Find or create rolling failure issue
67+
id: ensure_issue
68+
uses: actions/github-script@v7
69+
env:
70+
SUMMARY_PATH: ${{ steps.aggregate.outputs.summary_path }}
71+
with:
72+
github-token: ${{ secrets.GITHUB_TOKEN }}
73+
script: |
74+
const fs = require('fs');
75+
const body = fs.readFileSync(process.env.SUMMARY_PATH, 'utf8');
76+
const marker = '<!-- run:${{ github.event.workflow_run.id }} -->';
77+
const {owner, repo} = context.repo;
78+
const label = 'ci-failure';
79+
const title = 'Nightly CI failures: C# tests';
80+
81+
// Ensure label exists? Assume it's created manually as per repo policy.
82+
const {data: issues} = await github.rest.issues.listForRepo({
83+
owner, repo, state: 'open', labels: label, per_page: 100
84+
});
85+
let issue = issues.find(i => i.title === title);
86+
87+
if (!issue) {
88+
const created = await github.rest.issues.create({
89+
owner, repo, title, body, labels: [label]
90+
});
91+
core.setOutput('issue_number', String(created.data.number));
92+
core.info(`Created issue #${created.data.number}`);
93+
return;
94+
}
95+
96+
// Avoid duplicate comments for the same run using marker
97+
const {data: comments} = await github.rest.issues.listComments({
98+
owner, repo, issue_number: issue.number, per_page: 100
99+
});
100+
const exists = comments.some(c => c.body && c.body.includes(marker));
101+
if (!exists) {
102+
await github.rest.issues.createComment({
103+
owner, repo, issue_number: issue.number, body
104+
});
105+
core.info(`Commented on issue #${issue.number}`);
106+
} else {
107+
core.info('A comment for this run already exists; skipping.');
108+
}
109+
core.setOutput('issue_number', String(issue.number));

0 commit comments

Comments
 (0)