Skip to content

Commit 09a8557

Browse files
committed
Show long time warnings as GitHub annotations
1 parent 931cc5e commit 09a8557

File tree

4 files changed

+70
-19
lines changed

4 files changed

+70
-19
lines changed

src/sage/doctest/forker.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,8 @@ def _run(self, test, compileflags, out):
583583
Since it needs to be able to read stdout, it should be called
584584
while spoofing using :class:`SageSpoofInOut`.
585585
586+
INPUT: see :meth:`run`.
587+
586588
EXAMPLES::
587589
588590
sage: from sage.doctest.parsing import SageOutputChecker
@@ -628,6 +630,7 @@ def _run(self, test, compileflags, out):
628630
check = self._checker.check_output
629631

630632
# Process each example.
633+
example: doctest.Example
631634
for examplenum, example in enumerate(test.examples):
632635
if failures:
633636
# If exitfirst is set, abort immediately after a
@@ -1185,7 +1188,7 @@ def compile_and_execute(self, example, compiler, globs):
11851188
example.total_state = self.running_global_digest.hexdigest()
11861189
example.doctest_state = self.running_doctest_digest.hexdigest()
11871190

1188-
def _failure_header(self, test, example, message='Failed example:'):
1191+
def _failure_header(self, test, example, message='Failed example:', extra=None):
11891192
"""
11901193
We strip out ``sage:`` prompts, so we override
11911194
:meth:`doctest.DocTestRunner._failure_header` for better
@@ -1197,6 +1200,14 @@ def _failure_header(self, test, example, message='Failed example:'):
11971200
11981201
- ``example`` -- a :class:`doctest.Example` instance in ``test``
11991202
1203+
- ``message`` -- a message to be shown. Must not have a newline
1204+
1205+
- ``extra`` -- an extra message to be shown in GitHub annotation
1206+
1207+
Note that ``message`` and ``extra`` are not accepted by
1208+
:meth:`doctest.DocTestRunner._failure_header`, as such by Liskov
1209+
substitution principle this method must be callable without passing those.
1210+
12001211
OUTPUT: string used for reporting that the given example failed
12011212
12021213
EXAMPLES::
@@ -1260,6 +1271,8 @@ def _failure_header(self, test, example, message='Failed example:'):
12601271
message += ' [failed in baseline]'
12611272
else:
12621273
command = f'::error title={message}'
1274+
if extra:
1275+
message += f': {extra}'
12631276
if extra := getattr(example, 'extra', None):
12641277
message += f': {extra}'
12651278
if test.filename:
@@ -1561,12 +1574,12 @@ def report_overtime(self, out, test, example, got, *, check_timer=None):
15611574
Test ran for 1.23s cpu, 2.50s wall
15621575
Check ran for 2.34s cpu, 3.12s wall
15631576
"""
1564-
out(self._failure_header(test, example, 'Warning: slow doctest:') +
1565-
('Test ran for %.2fs cpu, %.2fs wall\nCheck ran for %.2fs cpu, %.2fs wall\n'
1566-
% (example.cputime,
1567-
example.walltime,
1568-
check_timer.cputime,
1569-
check_timer.walltime)))
1577+
time_info = ('Test ran for %.2fs cpu, %.2fs wall\nCheck ran for %.2fs cpu, %.2fs wall\n'
1578+
% (example.cputime,
1579+
example.walltime,
1580+
check_timer.cputime,
1581+
check_timer.walltime))
1582+
out(self._failure_header(test, example, 'Warning: slow doctest:', time_info) + time_info)
15701583

15711584
def report_unexpected_exception(self, out, test, example, exc_info):
15721585
r"""

src/sage/doctest/parsing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ def __ne__(self, other):
829829
"""
830830
return not (self == other)
831831

832-
def parse(self, string, *args):
832+
def parse(self, string, *args) -> list[doctest.Example | str]:
833833
r"""
834834
A Sage specialization of :class:`doctest.DocTestParser`.
835835
@@ -1015,8 +1015,8 @@ def parse(self, string, *args):
10151015
string = find_python_continuation.sub(r"\1" + ellipsis_tag + r"\2", string)
10161016
string = find_sage_prompt.sub(r"\1>>> sage: ", string)
10171017
string = find_sage_continuation.sub(r"\1...", string)
1018-
res = doctest.DocTestParser.parse(self, string, *args)
1019-
filtered = []
1018+
res: list[doctest.Example | str] = doctest.DocTestParser.parse(self, string, *args)
1019+
filtered: list[doctest.Example | str] = []
10201020
persistent_optional_tags = self.file_optional_tags
10211021
persistent_optional_tag_setter = None
10221022
persistent_optional_tag_setter_index = None

src/sage/doctest/sources.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def __ne__(self, other):
193193
"""
194194
return not (self == other)
195195

196-
def _process_doc(self, doctests, doc, namespace, start):
196+
def _process_doc(self, doctests: list[doctest.DocTest], doc, namespace, start):
197197
"""
198198
Appends doctests defined in ``doc`` to the list ``doctests``.
199199
@@ -266,7 +266,7 @@ def file_optional_tags(self):
266266
"""
267267
return set()
268268

269-
def _create_doctests(self, namespace, tab_okay=None):
269+
def _create_doctests(self, namespace, tab_okay=None) -> tuple[list[doctest.DocTest], dict]:
270270
"""
271271
Create a list of doctests defined in this source.
272272
@@ -314,7 +314,7 @@ def _create_doctests(self, namespace, tab_okay=None):
314314
probed_tags=self.options.probe,
315315
file_optional_tags=self.file_optional_tags)
316316
self.linking = False
317-
doctests = []
317+
doctests: list[doctest.DocTest] = []
318318
in_docstring = False
319319
unparsed_doc = False
320320
doc = []
@@ -480,7 +480,7 @@ def __iter__(self):
480480
for lineno, line in enumerate(self.source.split('\n')):
481481
yield lineno + self.lineno_shift, line + '\n'
482482

483-
def create_doctests(self, namespace):
483+
def create_doctests(self, namespace) -> tuple[list[doctest.DocTest], dict]:
484484
r"""
485485
Create doctests from this string.
486486
@@ -492,8 +492,8 @@ def create_doctests(self, namespace):
492492
493493
- ``doctests`` -- list of doctests defined by this string
494494
495-
- ``tab_locations`` -- either ``False`` or a list of linenumbers
496-
on which tabs appear
495+
- ``extras`` -- dictionary with ``extras['tab']`` either
496+
``False`` or a list of linenumbers on which tabs appear
497497
498498
EXAMPLES::
499499
@@ -503,10 +503,12 @@ def create_doctests(self, namespace):
503503
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
504504
sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
505505
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
506-
sage: dt, tabs = PSS.create_doctests({})
506+
sage: dt, extras = PSS.create_doctests({})
507507
sage: for t in dt:
508508
....: print("{} {}".format(t.name, t.examples[0].sage_source))
509509
<runtime> 2 + 2
510+
sage: extras
511+
{...'tab': []...}
510512
"""
511513
return self._create_doctests(namespace)
512514

@@ -736,7 +738,7 @@ def file_optional_tags(self):
736738
from .parsing import parse_file_optional_tags
737739
return parse_file_optional_tags(self)
738740

739-
def create_doctests(self, namespace):
741+
def create_doctests(self, namespace) -> tuple[list[doctest.DocTest], dict]:
740742
r"""
741743
Return a list of doctests for this file.
742744
@@ -910,7 +912,7 @@ class SourceLanguage:
910912
911913
Currently supported languages include Python, ReST and LaTeX.
912914
"""
913-
def parse_docstring(self, docstring, namespace, start):
915+
def parse_docstring(self, docstring, namespace, start) -> list[doctest.DocTest]:
914916
"""
915917
Return a list of doctest defined in this docstring.
916918

src/sage/doctest/test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,42 @@
4848
...
4949
0
5050
51+
Check slow doctest warnings are correctly raised::
52+
53+
sage: subprocess.call(["sage", "-t", "--warn-long", # long time
54+
....: "--random-seed=0", "--optional=sage", "sleep2.rst"], **kwds)
55+
Running doctests...
56+
Doctesting 1 file.
57+
sage -t --warn-long --random-seed=0 sleep2.rst
58+
**********************************************************************
59+
File "sleep2.rst", line 4, in sage.doctest.tests.sleep2
60+
Warning: slow doctest:
61+
while walltime(t) < 2: pass
62+
Test ran for ...s cpu, ...s wall
63+
Check ran for ...s cpu, ...s wall
64+
[2 tests, ...s wall]
65+
----------------------------------------------------------------------
66+
All tests passed!
67+
----------------------------------------------------------------------
68+
...
69+
0
70+
sage: subprocess.call(["sage", "-t", "--format=github", "--warn-long", # long time
71+
....: "--random-seed=0", "--optional=sage", "sleep2.rst"], **kwds)
72+
Running doctests...
73+
Doctesting 1 file.
74+
sage -t --warn-long --random-seed=0 sleep2.rst
75+
**********************************************************************
76+
::warning title=Warning: slow doctest:,file=sleep2.rst,line=4::slow doctest:: Test ran for ...s cpu, ...s wall%0ACheck ran for ...s cpu, ...s wall%0A
77+
while walltime(t) < 2: pass
78+
Test ran for ...s cpu, ...s wall
79+
Check ran for ...s cpu, ...s wall
80+
[2 tests, ...s wall]
81+
----------------------------------------------------------------------
82+
All tests passed!
83+
----------------------------------------------------------------------
84+
...
85+
0
86+
5187
Check handling of tolerances::
5288
5389
sage: subprocess.call(["python3", "-m", "sage.doctest", "--warn-long", "0", # long time

0 commit comments

Comments
 (0)