Skip to content

Commit 71fd528

Browse files
[ci] Include a log download link when test report is truncated (#117985)
Now "Download" will be a link to the file so people don't have to know to open the build tab and find the download button. This is a URL from a real build: https://buildkite.com/organizations/llvm-project/pipelines/github-pull-requests/builds/123979/jobs/01937132-0fc3-4c95-a884-2fc0048cb9a7/download.txt And this is how we can build it: https://buildkite.com/organizations/{BUILDKITE_ORGANIZATION_SLUG}/pipelines/{BUILDKITE_PIPELINE_SLUG}/builds/{BUILDKITE_BUILD_NUMBER}/jobs/{BUILDKITE_JOB_ID}/download.txt Given these env vars that were set in that job: BUILDKITE_ORGANIZATION_SLUG="llvm-project" BUILDKITE_PIPELINE_SLUG="github-pull-requests" BUILDKITE_BUILD_NUMBER="123979" BUILDKITE_JOB_ID="01937132-0fc3-4c95-a884-2fc0048cb9a7" In theory these will always be available but: 1. Rather safe than sorry with this script, I don't want to make a passing build a failure because this script failed. 2. It would get very annoying if you had to set all these to test the script locally.
1 parent a1ee1a9 commit 71fd528

File tree

1 file changed

+84
-6
lines changed

1 file changed

+84
-6
lines changed

.ci/generate_test_report.py

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# python3 -m unittest discover -p generate_test_report.py
66

77
import argparse
8+
import os
89
import subprocess
910
import unittest
1011
from io import StringIO
@@ -267,6 +268,46 @@ def test_report_dont_list_failures(self):
267268
),
268269
)
269270

271+
def test_report_dont_list_failures_link_to_log(self):
272+
self.assertEqual(
273+
_generate_report(
274+
"Foo",
275+
[
276+
junit_from_xml(
277+
dedent(
278+
"""\
279+
<?xml version="1.0" encoding="UTF-8"?>
280+
<testsuites time="0.02">
281+
<testsuite name="Bar" tests="1" failures="1" skipped="0" time="0.02">
282+
<testcase classname="Bar/test_1" name="test_1" time="0.02">
283+
<failure><![CDATA[Output goes here]]></failure>
284+
</testcase>
285+
</testsuite>
286+
</testsuites>"""
287+
)
288+
)
289+
],
290+
list_failures=False,
291+
buildkite_info={
292+
"BUILDKITE_ORGANIZATION_SLUG": "organization_slug",
293+
"BUILDKITE_PIPELINE_SLUG": "pipeline_slug",
294+
"BUILDKITE_BUILD_NUMBER": "build_number",
295+
"BUILDKITE_JOB_ID": "job_id",
296+
},
297+
),
298+
(
299+
dedent(
300+
"""\
301+
# Foo
302+
303+
* 1 test failed
304+
305+
Failed tests and their output was too large to report. [Download](https://buildkite.com/organizations/organization_slug/pipelines/pipeline_slug/builds/build_number/jobs/job_id/download.txt) the build's log file to see the details."""
306+
),
307+
"error",
308+
),
309+
)
310+
270311
def test_report_size_limit(self):
271312
self.assertEqual(
272313
_generate_report(
@@ -308,7 +349,13 @@ def test_report_size_limit(self):
308349
# listed. This minimal report will always fit into an annotation.
309350
# If include failures is False, total number of test will be reported but their names
310351
# and output will not be.
311-
def _generate_report(title, junit_objects, size_limit=1024 * 1024, list_failures=True):
352+
def _generate_report(
353+
title,
354+
junit_objects,
355+
size_limit=1024 * 1024,
356+
list_failures=True,
357+
buildkite_info=None,
358+
):
312359
if not junit_objects:
313360
return ("", "success")
314361

@@ -354,11 +401,21 @@ def plural(num_tests):
354401
report.append(f"* {tests_failed} {plural(tests_failed)} failed")
355402

356403
if not list_failures:
404+
if buildkite_info is not None:
405+
log_url = (
406+
"https://buildkite.com/organizations/{BUILDKITE_ORGANIZATION_SLUG}/"
407+
"pipelines/{BUILDKITE_PIPELINE_SLUG}/builds/{BUILDKITE_BUILD_NUMBER}/"
408+
"jobs/{BUILDKITE_JOB_ID}/download.txt".format(**buildkite_info)
409+
)
410+
download_text = f"[Download]({log_url})"
411+
else:
412+
download_text = "Download"
413+
357414
report.extend(
358415
[
359416
"",
360417
"Failed tests and their output was too large to report. "
361-
"Download the build's log file to see the details.",
418+
f"{download_text} the build's log file to see the details.",
362419
]
363420
)
364421
elif failures:
@@ -381,13 +438,23 @@ def plural(num_tests):
381438

382439
report = "\n".join(report)
383440
if len(report.encode("utf-8")) > size_limit:
384-
return _generate_report(title, junit_objects, size_limit, list_failures=False)
441+
return _generate_report(
442+
title,
443+
junit_objects,
444+
size_limit,
445+
list_failures=False,
446+
buildkite_info=buildkite_info,
447+
)
385448

386449
return report, style
387450

388451

389-
def generate_report(title, junit_files):
390-
return _generate_report(title, [JUnitXml.fromfile(p) for p in junit_files])
452+
def generate_report(title, junit_files, buildkite_info):
453+
return _generate_report(
454+
title,
455+
[JUnitXml.fromfile(p) for p in junit_files],
456+
buildkite_info=buildkite_info,
457+
)
391458

392459

393460
if __name__ == "__main__":
@@ -399,7 +466,18 @@ def generate_report(title, junit_files):
399466
parser.add_argument("junit_files", help="Paths to JUnit report files.", nargs="*")
400467
args = parser.parse_args()
401468

402-
report, style = generate_report(args.title, args.junit_files)
469+
# All of these are required to build a link to download the log file.
470+
env_var_names = [
471+
"BUILDKITE_ORGANIZATION_SLUG",
472+
"BUILDKITE_PIPELINE_SLUG",
473+
"BUILDKITE_BUILD_NUMBER",
474+
"BUILDKITE_JOB_ID",
475+
]
476+
buildkite_info = {k: v for k, v in os.environ.items() if k in env_var_names}
477+
if len(buildkite_info) != len(env_var_names):
478+
buildkite_info = None
479+
480+
report, style = generate_report(args.title, args.junit_files, buildkite_info)
403481

404482
if report:
405483
p = subprocess.Popen(

0 commit comments

Comments
 (0)