Skip to content

Commit 9097fa2

Browse files
author
Release Manager
committed
gh-37738: CI Build&Test: Show segfaults using GitHub annotations <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes #12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes #12345". --> Expanding the feature https://github.com/sagemath/sage/wiki/Sage-10.3-Release-Tour#doctest- failures-are-shown-in-the-files-changed-tab-on-prs to cover doctests that crash the doctest runner. An example (https://github.com/sagemath/sage/pull/37709/files) <img width="1078" alt="Screenshot 2024-04-03 at 11 14 05 AM" src="https://github.com/sagemath/sage/assets/8345221/de990f1f-069c-4fcf- ac7e-ed915687533b"> ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #37738 Reported by: Matthias Köppe Reviewer(s): Kwankyu Lee
2 parents 46b7ec2 + d605fa7 commit 9097fa2

File tree

1 file changed

+69
-17
lines changed

1 file changed

+69
-17
lines changed

src/sage/doctest/reporting.py

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
# https://www.gnu.org/licenses/
4242
# ****************************************************************************
4343

44+
import re
4445
from sys import stdout
4546
from signal import (SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL,
4647
SIGINT, SIGKILL, SIGPIPE, SIGQUIT, SIGSEGV, SIGTERM)
@@ -218,6 +219,69 @@ def report_head(self, source, fail_msg=None):
218219
cmd += f" [failed in baseline: {failed}]"
219220
return cmd
220221

222+
def _log_failure(self, source, fail_msg, event, output=None):
223+
r"""
224+
Report on the result of a failed doctest run.
225+
226+
INPUT:
227+
228+
- ``source`` -- a source from :mod:`sage.doctest.sources`
229+
230+
- ``fail_msg`` -- a string
231+
232+
- ``event`` -- a string
233+
234+
- ``output`` -- optional string
235+
236+
EXAMPLES::
237+
238+
sage: from sage.doctest.reporting import DocTestReporter
239+
sage: from sage.doctest.control import DocTestController, DocTestDefaults
240+
sage: from sage.doctest.sources import FileDocTestSource
241+
sage: from sage.env import SAGE_SRC
242+
sage: import os
243+
sage: filename = os.path.join(SAGE_SRC, 'sage', 'doctest', 'reporting.py')
244+
sage: DD = DocTestDefaults()
245+
sage: FDS = FileDocTestSource(filename, DD)
246+
sage: DC = DocTestController(DD,[filename])
247+
sage: DTR = DocTestReporter(DC)
248+
sage: DTR._log_failure(FDS, "Timed out", "process (pid=1234) timed out", "Output so far...")
249+
Timed out
250+
**********************************************************************
251+
Tests run before process (pid=1234) timed out:
252+
Output so far...
253+
**********************************************************************
254+
"""
255+
log = self.controller.log
256+
format = self.controller.options.format
257+
if format == 'sage':
258+
stars = "*" * 70
259+
log(f" {fail_msg}\n{stars}\n")
260+
if output:
261+
log(f"Tests run before {event}:")
262+
log(output)
263+
log(stars)
264+
elif format == 'github':
265+
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions
266+
command = f'::error title={fail_msg}'
267+
command += f',file={source.printpath}'
268+
if output:
269+
if m := re.search("## line ([0-9]+) ##\n-{40,100}\n(.*)", output, re.MULTILINE | re.DOTALL):
270+
lineno = m.group(1)
271+
message = m.group(2)
272+
command += f',line={lineno}'
273+
else:
274+
message = output
275+
# Urlencoding trick for multi-line annotations
276+
# https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448
277+
message = message.replace('\n', '%0A')
278+
else:
279+
message = ""
280+
command += f'::{message}'
281+
log(command)
282+
else:
283+
raise ValueError(f'unknown format option: {format}')
284+
221285
def report(self, source, timeout, return_code, results, output, pid=None):
222286
"""
223287
Report on the result of running doctests on a given source.
@@ -435,9 +499,7 @@ def report(self, source, timeout, return_code, results, output, pid=None):
435499
fail_msg += " (and interrupt failed)"
436500
else:
437501
fail_msg += " (with %s after interrupt)" % signal_name(sig)
438-
log(" %s\n%s\nTests run before %s timed out:" % (fail_msg, "*"*70, process_name))
439-
log(output)
440-
log("*"*70)
502+
self._log_failure(source, fail_msg, f"{process_name} timed out", output)
441503
postscript['lines'].append(self.report_head(source, fail_msg))
442504
stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests}
443505
if not baseline.get('failed', False):
@@ -449,9 +511,7 @@ def report(self, source, timeout, return_code, results, output, pid=None):
449511
fail_msg = "Killed due to %s" % signal_name(-return_code)
450512
if ntests > 0:
451513
fail_msg += " after testing finished"
452-
log(" %s\n%s\nTests run before %s failed:" % (fail_msg,"*"*70, process_name))
453-
log(output)
454-
log("*"*70)
514+
self._log_failure(source, fail_msg, f"{process_name} failed", output)
455515
postscript['lines'].append(self.report_head(source, fail_msg))
456516
stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests}
457517
if not baseline.get('failed', False):
@@ -466,15 +526,11 @@ def report(self, source, timeout, return_code, results, output, pid=None):
466526
else:
467527
cpu = 1e6
468528
if result_dict.err == 'badresult':
469-
log(" Error in doctesting framework (bad result returned)\n%s\nTests run before error:" % ("*"*70))
470-
log(output)
471-
log("*"*70)
529+
self._log_failure(source, "Error in doctesting framework (bad result returned)", "error", output)
472530
postscript['lines'].append(self.report_head(source, "Testing error: bad result"))
473531
self.error_status |= 64
474532
elif result_dict.err == 'noresult':
475-
log(" Error in doctesting framework (no result returned)\n%s\nTests run before error:" % ("*"*70))
476-
log(output)
477-
log("*"*70)
533+
self._log_failure(source, "Error in doctesting framework (no result returned)", "error", output)
478534
postscript['lines'].append(self.report_head(source, "Testing error: no result"))
479535
self.error_status |= 64
480536
elif result_dict.err == 'tab':
@@ -500,11 +556,7 @@ def report(self, source, timeout, return_code, results, output, pid=None):
500556
else:
501557
err = repr(result_dict.err)
502558
fail_msg = "%s in doctesting framework" % err
503-
504-
log(" %s\n%s" % (fail_msg, "*"*70))
505-
if output:
506-
log("Tests run before doctest exception:\n" + output)
507-
log("*"*70)
559+
self._log_failure(source, fail_msg, "exception", output)
508560
postscript['lines'].append(self.report_head(source, fail_msg))
509561
if hasattr(result_dict, 'tb'):
510562
log(result_dict.tb)

0 commit comments

Comments
 (0)