Generate HTML failure report and attach to Telegram on CI fail
When tests fail, ci-runner.sh generates an HTML report with: - Build metadata (branch, commit, actor, duration) - Test stats (passed/failed/skipped/errors) - Failed test table (file + test name) - Failure detail output ci-notify.sh sends the HTML as a document attachment alongside the text notification in the CICD Pipeline topic. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cfaf67c4c6
commit
ab89dc05b2
11
ci-notify.sh
11
ci-notify.sh
@ -18,6 +18,7 @@ SUMMARY="$4" # e.g., "940 passed, 0 failed, 393 skipped"
|
|||||||
FEATURES="$5" # e.g., "31/31 features verified"
|
FEATURES="$5" # e.g., "31/31 features verified"
|
||||||
DURATION="$6" # e.g., "12.3s"
|
DURATION="$6" # e.g., "12.3s"
|
||||||
ACTOR="$7" # who pushed
|
ACTOR="$7" # who pushed
|
||||||
|
REPORT_FILE="$8" # optional HTML failure report path
|
||||||
|
|
||||||
if [ "$STATUS" = "pass" ]; then
|
if [ "$STATUS" = "pass" ]; then
|
||||||
ICON="✅"
|
ICON="✅"
|
||||||
@ -58,4 +59,14 @@ curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
|||||||
-d "message_thread_id=${TOPIC_ID}" \
|
-d "message_thread_id=${TOPIC_ID}" \
|
||||||
-d "text=${MSG}" > /dev/null 2>&1
|
-d "text=${MSG}" > /dev/null 2>&1
|
||||||
|
|
||||||
|
# Send HTML failure report as attachment if available
|
||||||
|
if [ -n "$REPORT_FILE" ] && [ -f "$REPORT_FILE" ]; then
|
||||||
|
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendDocument" \
|
||||||
|
-F "chat_id=${CHAT_ID}" \
|
||||||
|
-F "message_thread_id=${TOPIC_ID}" \
|
||||||
|
-F "document=@${REPORT_FILE}" \
|
||||||
|
-F "caption=CI Failure Report — ${BRANCH} (${COMMIT})" > /dev/null 2>&1
|
||||||
|
rm -f "$REPORT_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
114
ci-runner.sh
114
ci-runner.sh
@ -147,9 +147,121 @@ echo "Features: $FEATURES_RESULT"
|
|||||||
echo "Duration: ${DURATION}s"
|
echo "Duration: ${DURATION}s"
|
||||||
echo "========================="
|
echo "========================="
|
||||||
|
|
||||||
|
# ─── Generate Failure Summary HTML ───
|
||||||
|
REPORT_FILE=""
|
||||||
|
if [ "$OVERALL" = "fail" ]; then
|
||||||
|
REPORT_FILE="/tmp/tsharps-ci-report-${BRANCH}-$(date +%Y%m%d-%H%M%S).html"
|
||||||
|
FAILED_LINES=$(echo "$TEST_OUTPUT" | grep "^FAILED " || true)
|
||||||
|
ERROR_LINES=$(echo "$TEST_OUTPUT" | grep "^ERROR " || true)
|
||||||
|
FAILURE_DETAILS=$(echo "$TEST_OUTPUT" | grep -B0 "^FAILED \|^E \|Error\|assert" | head -60 || true)
|
||||||
|
|
||||||
|
python3 -c "
|
||||||
|
import html, sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
branch = '$BRANCH'
|
||||||
|
commit = '$COMMIT'
|
||||||
|
actor = '$ACTOR'
|
||||||
|
duration = '${DURATION}s'
|
||||||
|
passed = '$PASS_COUNT'
|
||||||
|
failed = '$FAIL_COUNT'
|
||||||
|
skipped = '$SKIP_COUNT'
|
||||||
|
errors = '$ERROR_COUNT'
|
||||||
|
features = '''$FEATURES_RESULT'''
|
||||||
|
failed_lines = '''$FAILED_LINES'''
|
||||||
|
failure_details = '''$FAILURE_DETAILS'''
|
||||||
|
pkg_output = '''$PKG_OUTPUT'''
|
||||||
|
|
||||||
|
failed_items = [l.replace('FAILED ', '') for l in failed_lines.strip().split('\n') if l.strip()]
|
||||||
|
error_items = [l for l in '''$ERROR_LINES'''.strip().split('\n') if l.strip()]
|
||||||
|
|
||||||
|
rows = ''
|
||||||
|
for item in failed_items:
|
||||||
|
parts = item.rsplit('::', 1)
|
||||||
|
file_part = parts[0] if len(parts) > 0 else item
|
||||||
|
test_part = parts[1] if len(parts) > 1 else ''
|
||||||
|
rows += f'<tr><td><code>{html.escape(file_part)}</code></td><td><code>{html.escape(test_part)}</code></td></tr>\n'
|
||||||
|
|
||||||
|
for item in error_items:
|
||||||
|
rows += f'<tr><td colspan=\"2\"><code>{html.escape(item)}</code></td></tr>\n'
|
||||||
|
|
||||||
|
detail_block = html.escape(failure_details) if failure_details.strip() else '<em>No detailed output captured</em>'
|
||||||
|
pkg_block = html.escape(pkg_output) if pkg_output.strip() else ''
|
||||||
|
ts = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')
|
||||||
|
|
||||||
|
report = f'''<!DOCTYPE html>
|
||||||
|
<html lang=\"en\">
|
||||||
|
<head>
|
||||||
|
<meta charset=\"UTF-8\">
|
||||||
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||||
|
<title>CI Failure Report — {html.escape(branch)} ({html.escape(commit)})</title>
|
||||||
|
<style>
|
||||||
|
body {{ font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem; color: #1a1a2e; background: #f5f5f7; }}
|
||||||
|
h1 {{ color: #e53e3e; border-bottom: 3px solid #e53e3e; padding-bottom: 0.5rem; }}
|
||||||
|
.meta {{ background: #fff; border-radius: 8px; padding: 1rem 1.5rem; margin: 1rem 0; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
|
||||||
|
.meta table {{ width: 100%; border-collapse: collapse; }}
|
||||||
|
.meta td {{ padding: 0.3rem 0.5rem; }}
|
||||||
|
.meta td:first-child {{ font-weight: 600; width: 120px; color: #4a5568; }}
|
||||||
|
.stats {{ display: flex; gap: 1rem; flex-wrap: wrap; margin: 1rem 0; }}
|
||||||
|
.stat {{ background: #fff; padding: 0.75rem 1.25rem; border-radius: 6px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
|
||||||
|
.stat .num {{ font-size: 1.5rem; font-weight: 700; }}
|
||||||
|
.stat .label {{ font-size: 0.75rem; color: #718096; text-transform: uppercase; }}
|
||||||
|
.green {{ color: #38a169; }}
|
||||||
|
.red {{ color: #e53e3e; }}
|
||||||
|
.yellow {{ color: #d69e2e; }}
|
||||||
|
table.failures {{ width: 100%; border-collapse: collapse; margin: 1rem 0; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
|
||||||
|
table.failures th {{ background: #e53e3e; color: white; padding: 0.6rem 0.8rem; text-align: left; }}
|
||||||
|
table.failures td {{ padding: 0.5rem 0.8rem; border-bottom: 1px solid #e2e8f0; font-size: 0.9em; }}
|
||||||
|
table.failures tr:last-child td {{ border-bottom: none; }}
|
||||||
|
pre {{ background: #2d3748; color: #e2e8f0; padding: 1rem; border-radius: 8px; overflow-x: auto; font-size: 0.85em; line-height: 1.5; }}
|
||||||
|
code {{ background: #edf2f7; padding: 2px 6px; border-radius: 3px; font-size: 0.85em; }}
|
||||||
|
.footer {{ color: #a0aec0; font-size: 0.8rem; margin-top: 2rem; border-top: 1px solid #e2e8f0; padding-top: 0.5rem; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>CI Failure Report</h1>
|
||||||
|
|
||||||
|
<div class=\"meta\">
|
||||||
|
<table>
|
||||||
|
<tr><td>Branch</td><td><strong>{html.escape(branch)}</strong></td></tr>
|
||||||
|
<tr><td>Commit</td><td><code>{html.escape(commit)}</code></td></tr>
|
||||||
|
<tr><td>Pushed by</td><td>{html.escape(actor)}</td></tr>
|
||||||
|
<tr><td>Duration</td><td>{html.escape(duration)}</td></tr>
|
||||||
|
<tr><td>Features</td><td>{html.escape(features)}</td></tr>
|
||||||
|
<tr><td>Timestamp</td><td>{ts}</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=\"stats\">
|
||||||
|
<div class=\"stat\"><div class=\"num green\">{html.escape(passed)}</div><div class=\"label\">Passed</div></div>
|
||||||
|
<div class=\"stat\"><div class=\"num red\">{html.escape(failed)}</div><div class=\"label\">Failed</div></div>
|
||||||
|
<div class=\"stat\"><div class=\"num yellow\">{html.escape(skipped)}</div><div class=\"label\">Skipped</div></div>
|
||||||
|
<div class=\"stat\"><div class=\"num red\">{html.escape(errors)}</div><div class=\"label\">Errors</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Failed Tests</h2>
|
||||||
|
<table class=\"failures\">
|
||||||
|
<tr><th>File</th><th>Test</th></tr>
|
||||||
|
{rows}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Failure Details</h2>
|
||||||
|
<pre>{detail_block}</pre>
|
||||||
|
|
||||||
|
<div class=\"footer\">Generated by TSHARPS CI Runner — {ts}</div>
|
||||||
|
</body>
|
||||||
|
</html>'''
|
||||||
|
|
||||||
|
with open('$REPORT_FILE', 'w') as f:
|
||||||
|
f.write(report)
|
||||||
|
print(f'Report written to $REPORT_FILE')
|
||||||
|
" 2>&1
|
||||||
|
echo "Failure report: $REPORT_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── Send Notification ───
|
# ─── Send Notification ───
|
||||||
bash "$SCRIPT_DIR/ci-notify.sh" \
|
bash "$SCRIPT_DIR/ci-notify.sh" \
|
||||||
"$BRANCH" "$COMMIT" "$OVERALL" "$SUMMARY" "$FEATURES_RESULT" "${DURATION}s" "$ACTOR"
|
"$BRANCH" "$COMMIT" "$OVERALL" "$SUMMARY" "$FEATURES_RESULT" "${DURATION}s" "$ACTOR" "$REPORT_FILE"
|
||||||
|
|
||||||
# Release lock
|
# Release lock
|
||||||
flock -u 200
|
flock -u 200
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user