55# python3 -m unittest discover -p generate_test_report.py
66
77import argparse
8+ import os
89import subprocess
910import unittest
1011from 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
393460if __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