Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fa7db2e
Implement # sage.doctest: flaky marker
user202729 Feb 17, 2025
359bd0d
Mark a few files as flaky
user202729 Feb 17, 2025
8375fa5
Increase die_timeout
user202729 Feb 17, 2025
299a76b
Some missing documentation update
user202729 Feb 18, 2025
bd49fd2
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Feb 22, 2025
6cf82bd
Merge branch 'develop' into flaky-file
user202729 Mar 3, 2025
d5202db
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Mar 10, 2025
cc22c5a
Also handle flaky segmentation fault etc.
user202729 Mar 18, 2025
48f79d6
Some more markers
user202729 Mar 18, 2025
f8580bb
Fix some bugs
user202729 Mar 20, 2025
e0fc148
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Mar 27, 2025
7450eab
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Apr 19, 2025
6657bf8
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Apr 29, 2025
11b7094
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 May 18, 2025
fc287b9
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 May 19, 2025
4c8c5d4
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Jun 1, 2025
14acfc6
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Jun 27, 2025
a478463
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Jul 7, 2025
d682d70
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Jul 27, 2025
fe70ed1
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Aug 2, 2025
3db3e9c
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Aug 28, 2025
e9b1b35
Merge remote-tracking branch 'upstream/develop' into flaky-file
user202729 Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/sage/algebras/fusion_rings/fusion_ring.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# sage.doctest: flaky (:issue:`39538`)
"""
Fusion rings
"""
Expand Down
203 changes: 181 additions & 22 deletions src/sage/doctest/forker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1740,6 +1740,21 @@
"""
Create parallel :class:`DocTestWorker` processes and dispatches
doctesting tasks.

.. NOTE::

If this is run directly in the normal Sage command-line,
it calls :func:`init_sage` which in turn calls
:meth:`~sage.repl.rich_output.display_manager.DisplayManager.switch_backend`
to the doctest backend, which is incompatible with the IPython-based
command-line. As such, if an error such as
``TypeError: cannot unpack non-iterable NoneType object`` is seen,
a workaround is to run the following::

sage: # not tested
sage: from sage.repl.rich_output.backend_ipython import BackendIPythonCommandline
sage: backend = BackendIPythonCommandline()
sage: get_ipython().display_formatter.dm.switch_backend(backend, shell=get_ipython())
"""
def __init__(self, controller: DocTestController):
"""
Expand Down Expand Up @@ -1867,6 +1882,119 @@
1 of 1 in ...
[1 test, 1 failure, ...s wall]
Killing test ...

TESTS:

Test flaky files. This test should fail the first time and success the second time::

sage: # long time
sage: with NTF(suffix='.py', mode='w+t') as f1:

Check failure on line 1891 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmprv7x_i82.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmprv7x_i82.py [1 test, 0.01s wall]

Check failure on line 1891 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[a-f]*)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp1lh4ms2v.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp1lh4ms2v.py [1 test, 0.01s wall]
....: t = walltime()
....: _ = f1.write(f"# sage.doctest: flaky\n'''\nsage: sleep(10 if walltime() < {t+1} else 0)\n'''")
....: f1.flush()
....: DC = DocTestController(DocTestDefaults(timeout=2),
....: [f1.name])
....: DC.expand_files_into_sources()
....: DD = DocTestDispatcher(DC)
....: DR = DocTestReporter(DC)
....: DC.reporter = DR
....: DC.dispatcher = DD
....: DC.timer = Timer().start()
....: DD.parallel_dispatch()
sage -t ...
sage -t ...
[1 test, ...s wall]

Segmentation fault and abort are also handled::

sage: # long time
sage: with NTF(suffix='.py', mode='w+t') as f1:

Check failure on line 1911 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpukh5ytbk.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpukh5ytbk.py [2 tests, 0.00s wall]

Check failure on line 1911 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[a-f]*)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp8bn054vz.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp8bn054vz.py [2 tests, 0.01s wall]
....: t = walltime()
....: _ = f1.write(f"# sage.doctest: flaky\n"
....: f"'''\n"
....: f"sage: from cysignals.tests import unguarded_abort\n"
....: f"sage: if walltime() < {t+0.5r}: sleep(1r); unguarded_abort()\n"
....: f"'''\n")
....: f1.flush()
....: DC = DocTestController(DocTestDefaults(timeout=10),
....: [f1.name])
....: DC.expand_files_into_sources()
....: DD = DocTestDispatcher(DC)
....: DR = DocTestReporter(DC)
....: DC.reporter = DR
....: DC.dispatcher = DD
....: DC.timer = Timer().start()
....: DD.parallel_dispatch()
sage -t ...
sage -t ...
[2 tests, ...s wall]

This test always fail, so even flaky can't help it::

sage: # long time
sage: with NTF(suffix='.py', mode='w+t') as f1:

Check failure on line 1935 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp18ggeym6.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp18ggeym6.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp18ggeym6.py Timed out ********************************************************************** Tests run before process (pid=19269) timed out: sage: sleep(10) ## line 3 ## ------------------------------------------------------------------------ /usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x8ecc)[0x7f77e0505ecc] /usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x8f99)[0x7f77e0505f99] /usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0xb8e6)[0x7f77e05088e6] /lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7f77e0645330] /lib/x86_64-linux-gnu/libc.so.6(clock_nanosleep+0xbf)[0x7f77e06ecadf] python3(+0x332128)[0x560c8c608128] python3(+0x21b518)[0x560c8c4f1518] python3(PyObject_Vectorcall+0x2e)[0x560c8c4e7dde] python3(+0x112b23)[0x560c8c3e8b23] python3(PyEval_EvalCode+0xa1)[0x560c8c5861a1] python3(+0x2cc97c)[0x560c8c5a297c] python3(+0x11337b)[0x560c8c3e937b] python3(_PyObject_FastCallDictTstate+0x292)[0x560c8c4d7c12] python3(_PyObject_Call_Prepend+0x69)[0x560c8c500639] python3(+0x2f70cb)[0x560c8c5cd0cb] python3(_PyObject_MakeTpCall+0x2cc)[0x560c8c4d517c] python3(+0x112b23)[0x560c8c3e8b23] python3(_PyObject_FastCallDictTstate+0x1ee)[0x560c8c4d7b6e] python3(+0x22a21c)[0x560c8c50021c] python3(_PyObject_MakeTpCall+0x294)[0x560c8c4d5144] python3(+0x112b23)[0x560c8c3e8b23] python3(PyEval_EvalCode+0xa1)[0x560c8c5861a1] python3(+0x2cc97c)[0x560c8c5a297c] python3(+0x11337b)[0x560c8c3e937b] python3(_PyObject_FastCallDictTstate+0x292)[0x560c8c4d7c12] python3(_PyObject_Call_Prepend+0x69)[0x560c8c500639] python3(+0x2f70cb)[0x560c8c5cd0cb] python3(_PyObject_MakeTpCall+0x2cc)[0x560c8c4d517c] python3(+0x112b23)[0x560c8c3e8b23] python3(_PyObject_FastCallDictTstate+0x1ee)[0x560c8c4d7b6e] python3(+0x22a21c)[0x560c8c50021c] python3(_PyObject_MakeTpCall+0x294)[0x560c8c4d5144] python3(+0x112b23)[0x560c8c3e8b23] python3(PyEval_EvalCode+0xa1)[0x560c8c5861a1] python3(+0x2ea8ca)[0x560c8c5c08ca] python3(+0x2e5585)[0x560c8c5bb585] python3(+0x2e2620)[0x560c8c5b8620] python3(_PyRun_SimpleFileObject+0x1ce)[0x560c8c5b82be] python3(_PyRun_AnyFileObject+0x44)[0x560c8c5b7fe4] python3(Py_RunMain+0x3a2)[0x560c8c5b4eb2] python3(Py_BytesMain+0x37)[0x560c8c570247] /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)[0x7f77e062a1ca] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)[0x7f77e062a28b] python3(+0x29a0ed)[0x560c8c5700ed] ------------------------------------------------------------------------ Attaching gdb to process id 19269. Cannot find gdb installed GDB is not installed. Install gdb for enhanced tracebacks. ------------------------------------------------------------------------ **********************************************************************

Check failure on line 1935 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[a-f]*)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpcobx4gh7.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpcobx4gh7.py sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpcobx4gh7.py Timed out ********************************************************************** Tests run before process (pid=5124) timed out: sage: sleep(10) ## line 3 ## ------------------------------------------------------------------------ /sage/local/var/lib/sage/venv-python3.12/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x96a4)[0x7fb3bb41a6a4] /sage/local/var/lib/sage/venv-python3.12/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x9766)[0x7fb3bb41a766] /sage/local/var/lib/sage/venv-python3.12/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0xc451)[0x7fb3bb41d451] /lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7fb3bb9b5330] /lib/x86_64-linux-gnu/libc.so.6(clock_nanosleep+0xbf)[0x7fb3bba5cadf] python3[0x64cd1a] python3[0x581982] python3(PyObject_Vectorcall+0x35)[0x549cf5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3(_PyEval_EvalFrameDefault+0x3f56)[0x5d9d36] python3(_PyObject_Call_Prepend+0x18a)[0x54ac0a] python3[0x5a30c8] python3(_PyObject_MakeTpCall+0x13e)[0x5493be] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(_PyObject_Call_Prepend+0xc2)[0x54ab42] python3[0x59da4f] python3[0x599513] python3(_PyObject_MakeTpCall+0x75)[0x5492f5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3(_PyEval_EvalFrameDefault+0x3f56)[0x5d9d36] python3(_PyObject_Call_Prepend+0x18a)[0x54ac0a] python3[0x5a30c8] python3(_PyObject_MakeTpCall+0x13e)[0x5493be] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(_PyObject_Call_Prepend+0xc2)[0x54ab42] python3[0x59da4f] python3[0x599513] python3(_PyObject_MakeTpCall+0x75)[0x5492f5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3[0x5818ed] python3(PyObject_Vectorcall+0x35)[0x549cf5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3[0x5818ed] python3(PyObject_Vectorcall+0x35)[0x549cf5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3[0x6bc192] python3(Py_RunMain+0x232)[0x6bbdc2] python3(Py_BytesMain+0x2d)[0x6bba2d] /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)[0x7fb3bb99a1ca] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)[0x7fb3bb99a28b] python3(_start+0x25)[0x656a35] ------------------------------------------------------------------------ Attaching gdb to process id 5124. Cannot find gdb installed GDB is not installed. Install gdb for enhanced tracebacks. ------------------------------------------------------------------------ **********************************************************************
....: _ = f1.write(f"# sage.doctest: flaky\n'''\nsage: sleep(10)\n'''")
....: f1.flush()
....: DC = DocTestController(DocTestDefaults(timeout=2),
....: [f1.name])
....: DC.expand_files_into_sources()
....: DD = DocTestDispatcher(DC)
....: DR = DocTestReporter(DC)
....: DC.reporter = DR
....: DC.dispatcher = DD
....: DC.timer = Timer().start()
....: DD.parallel_dispatch()
sage -t ...
sage -t ...
Timed out
**********************************************************************
...

Of course without flaky, the test should fail (since it timeouts the first time)::

sage: # long time
sage: with NTF(suffix='.py', mode='w+t') as f1:

Check failure on line 1956 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpi578kbkv.py Timed out ********************************************************************** Tests run before process (pid=19319) timed out: sage: sleep(10 if walltime() < 1757606847.0854104 else 0) ## line 2 ## ------------------------------------------------------------------------ /usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x8ecc)[0x7f77e0505ecc] /usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x8f99)[0x7f77e0505f99] /usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0xb8e6)[0x7f77e05088e6] /lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7f77e0645330] /lib/x86_64-linux-gnu/libc.so.6(clock_nanosleep+0xbf)[0x7f77e06ecadf] python3(+0x332128)[0x560c8c608128] python3(+0x21b518)[0x560c8c4f1518] python3(PyObject_Vectorcall+0x2e)[0x560c8c4e7dde] python3(+0x112b23)[0x560c8c3e8b23] python3(PyEval_EvalCode+0xa1)[0x560c8c5861a1] python3(+0x2cc97c)[0x560c8c5a297c] python3(+0x11337b)[0x560c8c3e937b] python3(_PyObject_FastCallDictTstate+0x292)[0x560c8c4d7c12] python3(_PyObject_Call_Prepend+0x69)[0x560c8c500639] python3(+0x2f70cb)[0x560c8c5cd0cb] python3(_PyObject_MakeTpCall+0x2cc)[0x560c8c4d517c] python3(+0x112b23)[0x560c8c3e8b23] python3(_PyObject_FastCallDictTstate+0x1ee)[0x560c8c4d7b6e] python3(+0x22a21c)[0x560c8c50021c] python3(_PyObject_MakeTpCall+0x294)[0x560c8c4d5144] python3(+0x112b23)[0x560c8c3e8b23] python3(PyEval_EvalCode+0xa1)[0x560c8c5861a1] python3(+0x2cc97c)[0x560c8c5a297c] python3(+0x11337b)[0x560c8c3e937b] python3(_PyObject_FastCallDictTstate+0x292)[0x560c8c4d7c12] python3(_PyObject_Call_Prepend+0x69)[0x560c8c500639] python3(+0x2f70cb)[0x560c8c5cd0cb] python3(_PyObject_MakeTpCall+0x2cc)[0x560c8c4d517c] python3(+0x112b23)[0x560c8c3e8b23] python3(_PyObject_FastCallDictTstate+0x1ee)[0x560c8c4d7b6e] python3(+0x22a21c)[0x560c8c50021c] python3(_PyObject_MakeTpCall+0x294)[0x560c8c4d5144] python3(+0x112b23)[0x560c8c3e8b23] python3(PyEval_EvalCode+0xa1)[0x560c8c5861a1] python3(+0x2ea8ca)[0x560c8c5c08ca] python3(+0x2e5585)[0x560c8c5bb585] python3(+0x2e2620)[0x560c8c5b8620] python3(_PyRun_SimpleFileObject+0x1ce)[0x560c8c5b82be] python3(_PyRun_AnyFileObject+0x44)[0x560c8c5b7fe4] python3(Py_RunMain+0x3a2)[0x560c8c5b4eb2] python3(Py_BytesMain+0x37)[0x560c8c570247] /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)[0x7f77e062a1ca] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)[0x7f77e062a28b] python3(+0x29a0ed)[0x560c8c5700ed] ------------------------------------------------------------------------ Attaching gdb to process id 19319. Cannot find gdb installed GDB is not installed. Install gdb for enhanced tracebacks. ------------------------------------------------------------------------ **********************************************************************

Check failure on line 1956 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[a-f]*)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpkx526cwa.py Timed out ********************************************************************** Tests run before process (pid=5162) timed out: sage: sleep(10 if walltime() < 1757607797.0116751 else 0) ## line 2 ## ------------------------------------------------------------------------ /sage/local/var/lib/sage/venv-python3.12/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x96a4)[0x7fb3bb41a6a4] /sage/local/var/lib/sage/venv-python3.12/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0x9766)[0x7fb3bb41a766] /sage/local/var/lib/sage/venv-python3.12/lib/python3.12/site-packages/cysignals/signals.cpython-312-x86_64-linux-gnu.so(+0xc451)[0x7fb3bb41d451] /lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7fb3bb9b5330] /lib/x86_64-linux-gnu/libc.so.6(clock_nanosleep+0xbf)[0x7fb3bba5cadf] python3[0x64cd1a] python3[0x581982] python3(PyObject_Vectorcall+0x35)[0x549cf5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3(_PyEval_EvalFrameDefault+0x3f56)[0x5d9d36] python3(_PyObject_Call_Prepend+0x18a)[0x54ac0a] python3[0x5a30c8] python3(_PyObject_MakeTpCall+0x13e)[0x5493be] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(_PyObject_Call_Prepend+0xc2)[0x54ab42] python3[0x59da4f] python3[0x599513] python3(_PyObject_MakeTpCall+0x75)[0x5492f5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3(_PyEval_EvalFrameDefault+0x3f56)[0x5d9d36] python3(_PyObject_Call_Prepend+0x18a)[0x54ac0a] python3[0x5a30c8] python3(_PyObject_MakeTpCall+0x13e)[0x5493be] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(_PyObject_Call_Prepend+0xc2)[0x54ab42] python3[0x59da4f] python3[0x599513] python3(_PyObject_MakeTpCall+0x75)[0x5492f5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3[0x5818ed] python3(PyObject_Vectorcall+0x35)[0x549cf5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3(PyEval_EvalCode+0x15b)[0x5d4dab] python3[0x5d2bac] python3[0x5818ed] python3(PyObject_Vectorcall+0x35)[0x549cf5] python3(_PyEval_EvalFrameDefault+0xadf)[0x5d68bf] python3[0x6bc192] python3(Py_RunMain+0x232)[0x6bbdc2] python3(Py_BytesMain+0x2d)[0x6bba2d] /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)[0x7fb3bb99a1ca] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)[0x7fb3bb99a28b] python3(_start+0x25)[0x656a35] ------------------------------------------------------------------------ Attaching gdb to process id 5162. Cannot find gdb installed GDB is not installed. Install gdb for enhanced tracebacks. ------------------------------------------------------------------------ **********************************************************************
....: t = walltime()
....: _ = f1.write(f"'''\nsage: sleep(10 if walltime() < {t+1} else 0)\n'''")
....: f1.flush()
....: DC = DocTestController(DocTestDefaults(timeout=2),
....: [f1.name])
....: DC.expand_files_into_sources()
....: DD = DocTestDispatcher(DC)
....: DR = DocTestReporter(DC)
....: DC.reporter = DR
....: DC.dispatcher = DD
....: DC.timer = Timer().start()
....: DD.parallel_dispatch()
sage -t ...
Timed out
**********************************************************************
...

If it doesn't fail the first time, it must not be retried::

sage: from contextlib import redirect_stdout
sage: from io import StringIO
sage: with NTF(suffix='.py', mode='w+t') as f1, StringIO() as f, redirect_stdout(f):
....: t = walltime()
....: _ = f1.write(f"'''\nsage: sleep(0.5)\n'''")
....: f1.flush()
....: DC = DocTestController(DocTestDefaults(timeout=2),
....: [f1.name])
....: DC.expand_files_into_sources()
....: DD = DocTestDispatcher(DC)
....: DR = DocTestReporter(DC)
....: DC.reporter = DR
....: DC.dispatcher = DD
....: DC.timer = Timer().start()
....: DD.parallel_dispatch()
....: s = f.getvalue()
sage: print(s)

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, all)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp9wtwi0kx.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.11, all)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpazaoyhpq.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.11/site-packages/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/usr/share/miniconda/envs/sage-dev/lib/python3.11/site-packages/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/usr/share/miniconda/envs/sage-dev/lib/python3.11/site-packages/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmp5b7te4r3.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/usr/share/miniconda/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[a-f]*)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmpjhg91zhi.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/sage/src/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/sage/src/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/sage/src/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (macos, Python 3.11, all)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /var/folders/x7/ch5v91h56_zbvbd1y2f600dm0000gn/T/tmp34_pa4me.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/Users/runner/miniconda3/envs/sage-dev/lib/python3.11/site-packages/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/Users/runner/miniconda3/envs/sage-dev/lib/python3.11/site-packages/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/Users/runner/miniconda3/envs/sage-dev/lib/python3.11/site-packages/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, all, editable)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /tmp/tmprq8og7uk.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/home/runner/work/sage/sage/src/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/home/runner/work/sage/sage/src/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/home/runner/work/sage/sage/src/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno

Check failure on line 1992 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (macos, Python 3.12, all)

Failed example:

Failed example:: Got: sage-runtests --warn-long 5.0 --random-seed=0 /var/folders/x7/ch5v91h56_zbvbd1y2f600dm0000gn/T/tmpkamr51c2.py UnsupportedOperation in doctesting framework ********************************************************************** Traceback (most recent call last): File "/Users/runner/miniconda3/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 2771, in __call__ runner = SageDocTestRunner( ^^^^^^^^^^^^^^^^^^ File "/Users/runner/miniconda3/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 557, in __init__ self._fakeout = SageSpoofInOut(O) ^^^^^^^^^^^^^^^^^ File "/Users/runner/miniconda3/envs/sage-dev/lib/python3.12/site-packages/sage/doctest/forker.py", line 378, in __init__ self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "w") ^^^^^^^^^^^^^^^^^^^ io.UnsupportedOperation: fileno
sage -t ...
**********************************************************************
...
sage: s.count("sage -t")

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, all)

Failed example:

Failed example:: Got: 0

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.11, all)

Failed example:

Failed example:: Got: 0

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, new)

Failed example:

Failed example:: Got: 0

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / test-long (src/sage/[a-f]*)

Failed example:

Failed example:: Got: 0

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (macos, Python 3.11, all)

Failed example:

Failed example:: Got: 0

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu, Python 3.12, all, editable)

Failed example:

Failed example:: Got: 0

Check failure on line 1996 in src/sage/doctest/forker.py

View workflow job for this annotation

GitHub Actions / Conda (macos, Python 3.12, all)

Failed example:

Failed example:: Got: 0
1
"""
opt = self.controller.options

Expand Down Expand Up @@ -1911,11 +2039,11 @@
# List of alive DocTestWorkers (child processes). Workers which
# are done but whose messages have not been read are also
# considered alive.
workers = []
workers: list[DocTestWorker] = []

# List of DocTestWorkers which have finished running but
# whose results have not been reported yet.
finished = []
finished: list[DocTestWorker] = []

# If exitfirst is set and we got a failure.
abort_now = False
Expand Down Expand Up @@ -1953,6 +2081,28 @@
# precision.
now = time.time()

def start_new_worker(source: DocTestSource, num_retries_left: typing.Optional[int] = None) -> DocTestWorker:
nonlocal opt, target_endtime, now, pending_tests, sel_exit
import copy
worker_options = copy.copy(opt)
baseline = self.controller.source_baseline(source)
if target_endtime is not None:
worker_options.target_walltime = (target_endtime - now) / (max(1, pending_tests / opt.nthreads))
w = DocTestWorker(source, options=worker_options, funclist=[sel_exit], baseline=baseline)
if num_retries_left is not None:
w.num_retries_left = num_retries_left
elif 'flaky' in source.file_optional_tags:
w.num_retries_left = 2
heading = self.controller.reporter.report_head(w.source)
if not self.controller.options.only_errors:
w.messages = heading + "\n"
# Store length of heading to detect if the
# worker has something interesting to report.
w.heading_len = len(w.messages)
w.start() # This might take some time
w.deadline = time.time() + opt.timeout
return w

# If there were any substantial changes in the state
# (new worker started or finished worker reported),
# restart this while loop instead of calling pselect().
Expand All @@ -1965,7 +2115,7 @@
# "finished" list.
# Create a new list "new_workers" containing the active
# workers (to avoid updating "workers" in place).
new_workers = []
new_workers: list[DocTestWorker] = []
for w in workers:
if w.rmessages is not None or w.is_alive():
if now >= w.deadline:
Expand Down Expand Up @@ -2008,8 +2158,19 @@
workers = new_workers

# Similarly, process finished workers.
new_finished = []
new_finished: list[DocTestWorker] = []
for w in finished:
if (w.killed or w.result[1].err) and w.num_retries_left > 0:
# in this case, the messages from w should be suppressed
# (better handling could be implemented later)
# should also check w.killed or w.result if want to only retry
# on timeout/segmentation fault
# in that case w.source.file_optional_tags['flaky'] can be checked
if follow is w:
follow = None
workers.append(start_new_worker(w.source, w.num_retries_left - 1))
continue

if opt.exitfirst and w.result[1].failures:
abort_now = True
elif follow is not None and follow is not w:
Expand Down Expand Up @@ -2042,28 +2203,13 @@
while (source_iter is not None and len(workers) < opt.nthreads
and (not job_client or job_client.acquire())):
try:
source = next(source_iter)
source: DocTestSource = next(source_iter)
except StopIteration:
source_iter = None
if job_client:
job_client.release()
else:
# Start a new worker.
import copy
worker_options = copy.copy(opt)
baseline = self.controller.source_baseline(source)
if target_endtime is not None:
worker_options.target_walltime = (target_endtime - now) / (max(1, pending_tests / opt.nthreads))
w = DocTestWorker(source, options=worker_options, funclist=[sel_exit], baseline=baseline)
heading = self.controller.reporter.report_head(w.source)
if not self.controller.options.only_errors:
w.messages = heading + "\n"
# Store length of heading to detect if the
# worker has something interesting to report.
w.heading_len = len(w.messages)
w.start() # This might take some time
w.deadline = time.time() + opt.timeout
workers.append(w)
workers.append(start_new_worker(source))
restart = True

# Recompute state if needed
Expand Down Expand Up @@ -2240,13 +2386,20 @@
self.funclist = funclist
self.baseline = baseline

# This is not used by this class in any way, but DocTestDispatcher
# uses this to keep track of reruns for flaky tests.
self.num_retries_left = 0

# Open pipe for messages. These are raw file descriptors,
# not Python file objects!
self.rmessages, self.wmessages = os.pipe()

# Create Queue for the result. Since we're running only one
# doctest, this "queue" will contain only 1 element.
self.result_queue = multiprocessing.Queue(1)
self.result_queue: "Queue[tuple[int, DictAsObject]]" = multiprocessing.Queue(1)

# See :meth:`DocTestTask.__call__` for more information of this.
self.result: tuple[int, DictAsObject]

# Temporary file for stdout/stderr of the child process.
# Normally, this isn't used in the master process except to
Expand Down Expand Up @@ -2528,6 +2681,7 @@

EXAMPLES::

sage: # long time
sage: from sage.doctest.forker import DocTestTask
sage: from sage.doctest.sources import FileDocTestSource
sage: from sage.doctest.control import DocTestDefaults, DocTestController
Expand Down Expand Up @@ -2587,6 +2741,11 @@
doctests and ``result_dict`` is a dictionary annotated with
timings and error information.

``result_dict`` contains the key ``'err'`` (possibly ``None``),
and optionally contain the keys ``'walltime'``, ``'cputime'``,
``'walltime_skips'``, ``'tb'``, ``'tab_linenos'``, ``'optionals'``,
``'failures'``.

- Also put ``(doctests, result_dict)`` onto the ``result_queue``
if the latter isn't None.

Expand Down
34 changes: 33 additions & 1 deletion src/sage/doctest/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
)

special_optional_regex_raw = (
"py2|long time|not implemented|not tested|optional|needs|known bug"
"py2|long time|not implemented|not tested|optional|needs|known bug|flaky"
)
tag_with_explanation_regex_raw = r"((?:!?\w|[.])*)\s*(?:\((?P<cmd_explanation>.*?)\))?"
optional_regex: Pattern[str] = re.compile(
Expand Down Expand Up @@ -105,6 +105,7 @@ def parse_optional_tags(
- ``'not tested'``
- ``'known bug'`` (possible values are ``None``, ``linux`` and ``macos``)
- ``'py2'``
- ``'flaky'``
- ``'optional -- FEATURE...'`` or ``'needs FEATURE...'`` --
the dictionary will just have the key ``'FEATURE'``

Expand Down Expand Up @@ -174,6 +175,17 @@ def parse_optional_tags(
sage: parse_optional_tags("sage: #this is not #needs scipy\n....: import scipy",
....: return_string_sans_tags=True)
({'scipy': None}, 'sage: #this is not \n....: import scipy', False)

TESTS::

sage: parse_optional_tags("# flaky")
{'flaky': None}

Remember to update the documentation above whenever the following changes::

sage: from sage.doctest.parsing import special_optional_regex
sage: special_optional_regex.pattern
'py2|long time|not implemented|not tested|optional|needs|known bug|flaky'
"""
safe, literals, _ = strip_string_literals(string)
split = safe.split('\n', 1)
Expand Down Expand Up @@ -268,6 +280,11 @@ def parse_file_optional_tags(lines) -> dict[str, str | None]:
sage: with open(filename, "r") as f:
....: parse_file_optional_tags(enumerate(f))
{'xyz': None}
sage: with open(filename, "w") as f:
....: _ = f.write("# sage.doctest: flaky")
sage: with open(filename, "r") as f:
....: parse_file_optional_tags(enumerate(f))
{'flaky': None}
"""
tags: dict[str, str | None] = {}
for line_count, line in lines:
Expand Down Expand Up @@ -998,6 +1015,14 @@ def parse(self, string, *args) -> list[doctest.Example | str]:
'C.minimum_distance(algorithm="guava") # optional - guava\n'
sage: dte.want
'...\n24\n'

Test putting flaky tag in a single test::

sage: example5 = 'sage: 1 # flaky\n1'
sage: parsed5 = DTP.parse(example5)
Traceback (most recent call last):
...
NotImplementedError: 'flaky' tag is only implemented for whole file, not for single test
"""
# Regular expressions
find_sage_prompt = re.compile(r"^(\s*)sage: ", re.M)
Expand Down Expand Up @@ -1071,6 +1096,8 @@ def check_and_clear_tag_counts():
for item in res:
if isinstance(item, doctest.Example):
optional_tags_with_values, _, is_persistent = parse_optional_tags(item.source, return_string_sans_tags=True)
if "flaky" in optional_tags_with_values:
raise NotImplementedError("'flaky' tag is only implemented for whole file, not for single test")
optional_tags = set(optional_tags_with_values)
if is_persistent:
check_and_clear_tag_counts()
Expand All @@ -1095,6 +1122,11 @@ def check_and_clear_tag_counts():
('not tested' in optional_tags)):
continue

if 'flaky' in optional_tags:
# since a single test cannot have the 'flaky' tag,
# this must come from file_optional_tags
optional_tags.remove('flaky')

if 'long time' in optional_tags:
if self.long:
optional_tags.remove('long time')
Expand Down
1 change: 1 addition & 0 deletions src/sage/libs/singular/function.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# sage.doctest: flaky (:issue:`29528`)
"""
libSingular: Functions

Expand Down
1 change: 1 addition & 0 deletions src/sage/rings/polynomial/multi_polynomial_libsingular.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# sage.doctest: flaky
r"""
Multivariate Polynomials via libSINGULAR

Expand Down
1 change: 1 addition & 0 deletions src/sage/rings/polynomial/plural.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# sage.doctest: flaky (:issue:`29528`)
r"""
Noncommutative polynomials via libSINGULAR/Plural

Expand Down
1 change: 1 addition & 0 deletions src/sage/rings/polynomial/polynomial_element.pyx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# sage.doctest: flaky (:issue:`39183`)
"""
Univariate polynomial base class

Expand Down
Loading