Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace doctest runner by pytest in ci #36981

Draft
wants to merge 55 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
5fc47b2
Run pytest in conda ci
tobiasdiez Dec 29, 2023
a1f88a4
Fix missing feature import errors in SageDoctestModule
tobiasdiez Dec 29, 2023
af6959c
Skip when module not found error is raised during collection
tobiasdiez Dec 29, 2023
36eea75
Fix pytest command in build and ci-conda workflows
tobiasdiez Dec 29, 2023
bdfe4d8
Update pytest options in build and ci-conda workflows to show summary…
tobiasdiez Dec 29, 2023
fd809d9
Add rich output backend for doctest
tobiasdiez Dec 29, 2023
86defa1
Replace _get_runner with SageDocTestRunner in conftest.py
tobiasdiez Dec 29, 2023
8a6995e
Update pytest command to include verbose output
tobiasdiez Dec 29, 2023
bf6ac0d
Add pytest-github-actions-annotate-failures to test dependencies
tobiasdiez Dec 29, 2023
6c6582f
Only use sage spoofer if output file is specified
tobiasdiez Dec 30, 2023
96f9e58
Always run pytest
tobiasdiez Dec 30, 2023
038695a
Fix start_spoofing and stop_spoofing calls in SageDocTestRunner
tobiasdiez Dec 30, 2023
490146f
Refactor exception printing to use short names instead of Sage's hack…
tobiasdiez Dec 30, 2023
a94a678
add missing imports
tobiasdiez Dec 30, 2023
1c6fa26
Add imports to conftest.py
tobiasdiez Dec 30, 2023
ad7a14b
revert changes to forker
tobiasdiez Dec 30, 2023
6a77fd7
Fix a few display errors
tobiasdiez Dec 30, 2023
4d3f882
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Feb 11, 2024
2666c6b
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Feb 25, 2024
fb70abe
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Oct 30, 2024
8f8ff88
Run tests using pytest in meson workflow
tobiasdiez Oct 30, 2024
30a3af5
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Nov 18, 2024
ba98646
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Nov 29, 2024
4779f6c
Fix errors
tobiasdiez Nov 30, 2024
90af29a
Clarify todo
tobiasdiez Dec 11, 2024
749202e
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Dec 11, 2024
d8ddedc
Fix meson run
tobiasdiez Dec 11, 2024
f86f833
Fix deprecation warning capturing by printing to stdout by default
tobiasdiez Dec 11, 2024
c4210dd
Add missing variables to global namespace
tobiasdiez Dec 11, 2024
afb6cf9
Exclude test file
tobiasdiez Dec 11, 2024
9c231a9
Ignore more optional/executable files
tobiasdiez Dec 12, 2024
74520cb
Run tests in isolation
tobiasdiez Dec 12, 2024
6f9861f
Fix a few r-strings
tobiasdiez Dec 13, 2024
a3845f6
Fix exception printing also in isolated mode
tobiasdiez Dec 13, 2024
c781ad8
Ignore import errors also on meson
tobiasdiez Dec 13, 2024
ddba82f
Rename methods prefixed with `test_` for pytest compatibility
tobiasdiez Dec 16, 2024
678142d
Set globals in pytests
tobiasdiez Dec 16, 2024
942db44
Add more r-string
tobiasdiez Dec 16, 2024
bd7ce3c
More renames
tobiasdiez Dec 16, 2024
92b162e
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Dec 16, 2024
31472b9
Reset name
tobiasdiez Dec 16, 2024
a016921
fix test -> dummy
tobiasdiez Dec 16, 2024
f69ec04
Try to fix CI
tobiasdiez Dec 16, 2024
df6293f
Add _make_named_class_key for FilteredModulesCategory
user202729 Dec 18, 2024
c6b3b55
Fix a few things
user202729 Dec 19, 2024
c14e958
Fix tests
user202729 Dec 19, 2024
775e4bb
Merge branch 'pr/user202729/39160' into pytest_replace_doctest
tobiasdiez Dec 20, 2024
3e2b0ee
No longer run isolated
tobiasdiez Dec 20, 2024
13e9c3e
Exclude more problematic files
tobiasdiez Dec 20, 2024
83bf9aa
Move bitness check to doctest parser/output checker
tobiasdiez Dec 21, 2024
366925f
Move pytest config to root
tobiasdiez Dec 21, 2024
0d29653
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Jan 4, 2025
9e45489
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Feb 28, 2025
0a0c767
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Mar 7, 2025
88eed5e
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Mar 13, 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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,6 @@ jobs:
GH_TOKEN: ${{ github.token }}

# Building

- name: Generate Dockerfile
# From docker.yml
run: |
Expand Down Expand Up @@ -469,6 +468,7 @@ jobs:
--workdir $(pwd) \
${{ env.BUILD_IMAGE }} /bin/sh


# Combining

- name: Download coverage artifacts
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/ci-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ jobs:
activate-environment: sage-dev
environment-file: ${{ matrix.conda-env }}-${{ matrix.python }}-${{ startsWith(matrix.os, 'macos') && (startsWith(runner.arch, 'ARM') && 'macos' || 'macos-x86_64') || 'linux' }}.yml

- name: Install test tools
shell: bash -l {0}
run: |
conda install -c conda-forge pytest-xdist pytest-github-actions-annotate-failures coverage
pip install pytest-isolate

- name: Print Conda environment
shell: bash -l {0}
run: |
Expand Down Expand Up @@ -89,7 +95,7 @@ jobs:
- name: Test
if: success() || failure()
shell: bash -l {0}
run: ./sage -t --all --baseline-stats-path=.github/workflows/ci-conda-known-test-failures.json -p0
run: ./sage -python -m pytest --doctest-ignore-import-errors --doctest -rfEs -s src

- name: Print logs
if: always()
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci-meson.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ jobs:
# If editable then deleting the directory will cause sage to detect rebuild, which will cause ninja to fail
# so we don't delete the directory in this case
${{ matrix.editable && 'true' || 'rm -R ./src/sage_setup/' }}
./sage -t --all -p4 --format github
pip install coverage pytest-xdist pytest-github-actions-annotate-failures pytest-isolate
pytest --doctest-ignore-import-errors --doctest -rfEs -s src

- name: Upload log
uses: actions/[email protected]
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"arctanh",
"Bejger",
"bigcup",
"bitness",
"cachefunc",
"charpoly",
"classmethod",
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def _find(
except ModuleNotFoundError as exception:
# TODO: Remove this once all optional things are using Features
pytest.skip(
f"unable to import module { self.path } due to missing module { exception.name }"
f"unable to import module { self.path } due to missing feature { exception.name }"
)


Expand Down
4 changes: 2 additions & 2 deletions src/doc/en/developer/doctesting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ as taking a long time:
on machines with "only" 2GB of RAM, we test ``max_n`` = 1, which
has a more reasonable memory usage. ::

sage: from sage.crypto.mq.sr import test_consistency
sage: test_consistency(1) # long time (80s on sage.math, 2011)
sage: from sage.crypto.mq.sr import check_consistency
sage: check_consistency(1) # long time (80s on sage.math, 2011)
True
"""

Expand Down
6 changes: 3 additions & 3 deletions src/sage/algebras/fusion_rings/fusion_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@


class FusionRing(WeylCharacterRing):
r"""

Check failure on line 36 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

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

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing

Check failure on line 36 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu-latest, 3.11, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing

Check failure on line 36 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu-latest, 3.12, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing

Check failure on line 36 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (macos-latest, 3.11, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing

Check failure on line 36 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (macos-latest, 3.12, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing
Return the Fusion Ring (Verlinde Algebra) of level ``k``.

INPUT:
Expand Down Expand Up @@ -381,7 +381,7 @@
tester.assertTrue(tqo.is_real_positive())
tester.assertEqual(tqo**2, self.global_q_dimension(base_coercion=False))

def test_braid_representation(self, max_strands=6, anyon=None):
def check_braid_representation(self, max_strands=6, anyon=None):
"""
Check that we can compute valid braid group representations.

Expand All @@ -402,10 +402,10 @@
EXAMPLES::

sage: A21 = FusionRing("A2", 1)
sage: A21.test_braid_representation(max_strands=4)
sage: A21.check_braid_representation(max_strands=4)
True
sage: F41 = FusionRing("F4", 1) # long time
sage: F41.test_braid_representation() # long time
sage: F41.check_braid_representation() # long time
True
"""
if not self.is_multiplicity_free(): # Braid group representation is not available if self is not multiplicity free
Expand Down Expand Up @@ -524,7 +524,7 @@
return CyclotomicField(4 * self._cyclotomic_order)

def fvars_field(self):
r"""

Check failure on line 527 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

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

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.fvars_field

Check failure on line 527 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu-latest, 3.11, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.fvars_field

Check failure on line 527 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu-latest, 3.12, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.fvars_field

Check failure on line 527 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (macos-latest, 3.11, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.fvars_field

Check failure on line 527 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (macos-latest, 3.12, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.fvars_field
Return a field containing the ``CyclotomicField`` computed by
:meth:`field` as well as all the F-symbols of the associated
``FMatrix`` factory object.
Expand Down Expand Up @@ -1415,7 +1415,7 @@
A class for FusionRing elements.
"""
def is_simple_object(self):
r"""

Check failure on line 1418 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

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

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.Element.is_simple_object

Check failure on line 1418 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu-latest, 3.11, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.Element.is_simple_object

Check failure on line 1418 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (ubuntu-latest, 3.12, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.Element.is_simple_object

Check failure on line 1418 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (macos-latest, 3.11, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.Element.is_simple_object

Check failure on line 1418 in src/sage/algebras/fusion_rings/fusion_ring.py

View workflow job for this annotation

GitHub Actions / Conda (macos-latest, 3.12, environment)

[doctest] sage.algebras.fusion_rings.fusion_ring.FusionRing.Element.is_simple_object
Determine whether ``self`` is a simple object of the fusion ring.

EXAMPLES::
Expand Down
1 change: 1 addition & 0 deletions src/sage/categories/cartesian_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class CartesianProductFunctor(CovariantFunctorialConstruction, MultivariateConst
sage: C = cartesian_product([M, ZZ, QQ])
sage: C
The Cartesian product of (M, Integer Ring, Rational Field)
sage: M.reset_name()
sage: C.an_element()
('abcd', 1, 1/2)
sage: C.an_element()^2
Expand Down
26 changes: 13 additions & 13 deletions src/sage/categories/category_with_axiom.py
Original file line number Diff line number Diff line change
Expand Up @@ -2534,29 +2534,29 @@
:class:`Category_over_base_ring` becomes automatically a
:class:`CategoryWithAxiom_over_base_ring`::

sage: from sage.categories.category_with_axiom import TestObjectsOverBaseRing, Category_over_base_ring
sage: from sage.categories.category_with_axiom import DummyObjectsOverBaseRing, Category_over_base_ring
sage: from sage.categories.category import JoinCategory
sage: isinstance(TestObjectsOverBaseRing(QQ), Category_over_base_ring)
sage: isinstance(DummyObjectsOverBaseRing(QQ), Category_over_base_ring)
True
sage: C = TestObjectsOverBaseRing(QQ).Commutative()
sage: C = DummyObjectsOverBaseRing(QQ).Commutative()
sage: isinstance(C, Category_over_base_ring) # todo: not implemented
True
sage: C.FiniteDimensional()

Check failure on line 2544 in src/sage/categories/category_with_axiom.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Category of finite dimensional commutative dummy objects over base ring over Rational Field
Category of finite dimensional commutative test objects over base ring over Rational Field
sage: C.Commutative()

Check failure on line 2546 in src/sage/categories/category_with_axiom.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Category of commutative dummy objects over base ring over Rational Field
Category of commutative test objects over base ring over Rational Field
sage: C.Unital()

Check failure on line 2548 in src/sage/categories/category_with_axiom.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Category of commutative unital dummy objects over base ring over Rational Field
Category of commutative unital test objects over base ring over Rational Field

sage: C = TestObjectsOverBaseRing(IntegerModRing(2)).Connected()
sage: C = DummyObjectsOverBaseRing(IntegerModRing(2)).Connected()
sage: isinstance(C, JoinCategory)
True
sage: isinstance(C, Category_over_base_ring) # todo: not implemented
True
sage: C.FiniteDimensional()

Check failure on line 2556 in src/sage/categories/category_with_axiom.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Category of finite dimensional connected dummy objects over base ring over Ring of integers modulo 2
Category of finite dimensional connected test objects
over base ring over Ring of integers modulo 2
sage: C.Connected()

Check failure on line 2559 in src/sage/categories/category_with_axiom.py

View workflow job for this annotation

GitHub Actions / test-new

Failed example:

Failed example:: Got: Category of connected dummy objects over base ring over Ring of integers modulo 2
Category of connected test objects over base ring over Ring of integers modulo 2
"""

Expand Down Expand Up @@ -2604,7 +2604,7 @@

- :class:`Bars`
- :class:`TestObjects`
- :class:`TestObjectsOverBaseRing`
- :class:`DummyObjectsOverBaseRing`
"""

def super_categories(self):
Expand Down Expand Up @@ -2788,7 +2788,7 @@
pass


class TestObjectsOverBaseRing(Category_over_base_ring):
class DummyObjectsOverBaseRing(Category_over_base_ring):
r"""
A toy singleton category, for testing purposes.

Expand All @@ -2799,14 +2799,14 @@
"""
TESTS::

sage: from sage.categories.category_with_axiom import TestObjectsOverBaseRing
sage: TestObjectsOverBaseRing(QQ).super_categories()
sage: from sage.categories.category_with_axiom import DummyObjectsOverBaseRing
sage: DummyObjectsOverBaseRing(QQ).super_categories()
[Category of test objects]
sage: TestObjectsOverBaseRing.Unital.an_instance()
Category of unital test objects over base ring over Rational Field
sage: TestObjectsOverBaseRing.FiniteDimensional.Unital.an_instance()
Category of finite dimensional unital test objects over base ring over Rational Field
sage: C = TestObjectsOverBaseRing(QQ).FiniteDimensional().Unital().Commutative()
sage: DummyObjectsOverBaseRing.Unital.an_instance()
Category of unital dummy objects over base ring over Rational Field
sage: DummyObjectsOverBaseRing.FiniteDimensional.Unital.an_instance()
Category of finite dimensional unital dummy objects over base ring over Rational Field
sage: C = DummyObjectsOverBaseRing(QQ).FiniteDimensional().Unital().Commutative()
sage: TestSuite(C).run()
"""
return [TestObjects()]
Expand Down
6 changes: 3 additions & 3 deletions src/sage/crypto/mq/sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3298,7 +3298,7 @@ def __exit__(self, typ, value, tb):
self.sr._allow_zero_inversions = self.allow_zero_inversions


def test_consistency(max_n=2, **kwargs):
def check_consistency(max_n=2, **kwargs):
r"""
Test all combinations of ``r``, ``c``, ``e`` and ``n`` in ``(1,
2)`` for consistency of random encryptions and their polynomial
Expand All @@ -3317,8 +3317,8 @@ def test_consistency(max_n=2, **kwargs):
on machines with "only" 2GB of RAM, we test ``max_n`` = 1, which
has a more reasonable memory usage. ::

sage: from sage.crypto.mq.sr import test_consistency
sage: test_consistency(1) # long time (65s on sage.math, 2012)
sage: from sage.crypto.mq.sr import check_consistency
sage: check_consistency(1) # long time (65s on sage.math, 2012)
True
"""
consistent = True
Expand Down
6 changes: 3 additions & 3 deletions src/sage/databases/oeis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1928,7 +1928,7 @@ def flush_to_table(language, code_lines):
return sorted(table)
return sorted(prog for la, prog in table if la == language)

def test_compile_sage_code(self):
def check_compile_sage_code(self):
"""
Try to compile the extracted sage code, if there is any.

Expand All @@ -1944,13 +1944,13 @@ def test_compile_sage_code(self):
One correct sequence::

sage: s = oeis.find_by_id('A027642') # optional -- internet
sage: s.test_compile_sage_code() # optional -- internet
sage: s.check_compile_sage_code() # optional -- internet
True

One dead sequence::

sage: s = oeis.find_by_id('A000154') # optional -- internet
sage: s.test_compile_sage_code() # optional -- internet
sage: s.check_compile_sage_code() # optional -- internet
True
"""
if self.is_dead():
Expand Down
41 changes: 11 additions & 30 deletions src/sage/doctest/forker.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def showwarning_with_traceback(message, category, filename, lineno, file=None, l
lines.extend(traceback.format_exception_only(category, category(message)))

if file is None:
file = sys.stderr
file = sys.stdout
try:
file.writelines(lines)
file.flush()
Expand Down Expand Up @@ -517,7 +517,7 @@ def getvalue(self):
TestResults = namedtuple('TestResults', 'failed attempted')


class SageDocTestRunner(doctest.DocTestRunner):
class SageDocTestRunner(doctest.DocTestRunner, object):
def __init__(self, *args, **kwds):
"""
A customized version of DocTestRunner that tracks dependencies
Expand Down Expand Up @@ -706,10 +706,10 @@ def compiler(example):
# findlinestarts() returns pairs (index, lineno) where
# "index" is the index in the bytecode where the line
# number changes to "lineno".
linenumbers1 = {lineno for (index, lineno)
in findlinestarts(code)}
linenumbers2 = {lineno for (index, lineno)
in findlinestarts(execcode)}
linenumbers1 = set(lineno for (index, lineno)
in findlinestarts(code))
linenumbers2 = set(lineno for (index, lineno)
in findlinestarts(execcode))
if linenumbers1 != linenumbers2:
raise SyntaxError("doctest is not a single statement")

Expand Down Expand Up @@ -1855,17 +1855,6 @@ def parallel_dispatch(self):
"""
opt = self.controller.options

job_client = None
try:
from gnumake_tokenpool import JobClient, NoJobServer
except ImportError:
pass
else:
try:
job_client = JobClient(use_cysignals=True)
except NoJobServer:
pass

source_iter = iter(self.controller.sources)

# If timeout was 0, simply set a very long time
Expand Down Expand Up @@ -1987,9 +1976,6 @@ def sel_exit():
w.copied_pid = w.pid
w.close()
finished.append(w)
if job_client:
job_client.release()

workers = new_workers

# Similarly, process finished workers.
Expand Down Expand Up @@ -2024,14 +2010,11 @@ def sel_exit():
break

# Start new workers if possible
while (source_iter is not None and len(workers) < opt.nthreads
and (not job_client or job_client.acquire())):
while source_iter is not None and len(workers) < opt.nthreads:
try:
source = next(source_iter)
except StopIteration:
source_iter = None
if job_client:
job_client.release()
else:
# Start a new worker.
import copy
Expand Down Expand Up @@ -2108,8 +2091,6 @@ def sel_exit():
sleep(die_timeout)
for w in workers:
w.kill()
if job_client:
job_client.release()
finally:
os._exit(0)

Expand Down Expand Up @@ -2403,7 +2384,7 @@ def save_result_output(self):
try:
self.result = self.result_queue.get(block=False)
except Empty:
self.result = (0, DictAsObject({'err': 'noresult'}))
self.result = (0, DictAsObject(dict(err='noresult')))
del self.result_queue

self.outtmpfile.seek(0)
Expand Down Expand Up @@ -2601,8 +2582,8 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None, *,
runner.basename = self.source.basename
runner.filename = self.source.path
N = options.file_iterations
results = DictAsObject({'walltime': [], 'cputime': [],
'err': None, 'walltime_skips': 0})
results = DictAsObject(dict(walltime=[], cputime=[],
err=None, walltime_skips=0))

# multiprocessing.Process instances don't run exit
# functions, so we run the functions added by doctests
Expand All @@ -2627,7 +2608,7 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None, *,
except BaseException:
exc_info = sys.exc_info()
tb = "".join(traceback.format_exception(*exc_info))
result = (0, DictAsObject({'err': exc_info[0], 'tb': tb}))
result = (0, DictAsObject(dict(err=exc_info[0], tb=tb)))

if result_queue is not None:
result_queue.put(result, False)
Expand Down
27 changes: 24 additions & 3 deletions src/sage/doctest/marked_output.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# sage_setup: distribution = sagemath-repl
"""
Helper for attaching tolerance information to strings
Helper for attaching metadata to doctest output.
"""

# ****************************************************************************
Expand All @@ -24,6 +24,19 @@
# https://www.gnu.org/licenses/
# ****************************************************************************

from typing import TypedDict

from typing_extensions import NotRequired, Unpack


class MarkedOutputType(TypedDict):
random: NotRequired[bool]
rel_tol: NotRequired[float]
abs_tol: NotRequired[float]
tol: NotRequired[float]
bitness_32: NotRequired[str]
bitness_64: NotRequired[str]


class MarkedOutput(str):
"""
Expand All @@ -44,12 +57,15 @@ class MarkedOutput(str):
sage: MarkedOutput("56 µs")
'56 \xb5s'
"""

random = False
rel_tol = 0
abs_tol = 0
tol = 0
bitness_32 = ""
bitness_64 = ""

def update(self, **kwds):
def update(self, **kwargs: Unpack[MarkedOutputType]):
"""
EXAMPLES::

Expand All @@ -62,7 +78,7 @@ def update(self, **kwds):
sage: s.abs_tol
1.00000000000000e-7
"""
self.__dict__.update(kwds)
self.__dict__.update(kwargs)
return self

def __reduce__(self):
Expand All @@ -83,6 +99,11 @@ def __reduce__(self):
"""
return make_marked_output, (str(self), self.__dict__)

def __eq__(self, value: object) -> bool:
if isinstance(value, MarkedOutput):
return str(self) == str(value) and self.__dict__ == value.__dict__
return False


def make_marked_output(s, D):
"""
Expand Down
Loading
Loading