Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 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
4bd1d61
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Mar 23, 2025
c10774d
Ignore `all.py` in pytest collection to prevent import issues
tobiasdiez Mar 23, 2025
f2ee411
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Mar 29, 2025
619abdd
Ignore lie algebras for now due to CI errors
tobiasdiez Mar 30, 2025
f48c30e
Activate pyx parsing (and fix indention warnings)
tobiasdiez Mar 30, 2025
107c56d
Ignore lie_conformal_algebras for now
tobiasdiez Mar 30, 2025
268fa58
Ignore all of algebras for now
tobiasdiez Mar 31, 2025
40d32dd
And ignore calculus
tobiasdiez Mar 31, 2025
00a554a
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Apr 3, 2025
c7b99d6
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez May 13, 2025
1b1a230
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez May 20, 2025
73c63bc
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Jun 4, 2025
42490cb
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Jun 15, 2025
892670d
Don't try to import `src/sage_docbuild/build_options.py`
tobiasdiez Jun 16, 2025
9967f48
Ignore `builders.py` in addition to `build_options.py` for Meson comp…
tobiasdiez Jun 16, 2025
a5dacb9
Ignore more fatal python errors
tobiasdiez Jun 16, 2025
dc37fa6
Ignore `atexit.pyx` in `cpython` directory to prevent fatal Python er…
tobiasdiez Jun 16, 2025
dd13d79
Conditional pytest-isolate installation not on Windows
tobiasdiez Jun 16, 2025
19295a0
Rename more `test_` to `check_`
tobiasdiez Jun 16, 2025
f549d82
Improve error message for skipped doctests on import failure
tobiasdiez Jun 16, 2025
0af1a89
Fix condition for installing pytest-isolate based on matrix OS
tobiasdiez Jun 16, 2025
c1da4b3
Enhance pytest command with full trace option for better error visibi…
tobiasdiez Jun 16, 2025
6faf68b
Ignore specific files in pytest collection to prevent output stream i…
tobiasdiez Jun 16, 2025
1eb59cb
Remove incompatible test
tobiasdiez Jun 16, 2025
47f56b1
Fix docstring formatting to raw string literals
tobiasdiez Jun 16, 2025
79f434f
Add ignore condition for calculus_method.py in pytest collection
tobiasdiez Jun 16, 2025
a6e05f9
Prevent fatal errors in specific test files
tobiasdiez Jun 17, 2025
cbd89a9
Add more to pytest ignore logic to prevent fatal errors
tobiasdiez Jun 17, 2025
ddf825f
Exclude a few more files
tobiasdiez Jun 18, 2025
e0eca45
Merge branch 'pytest_rename_check' into pytest_replace_doctest
tobiasdiez Jun 30, 2025
5388732
Merge branch 'maxima-bin-remove' into pytest_replace_doctest
tobiasdiez Jun 30, 2025
e3040a2
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Jul 6, 2025
faa2bdf
Merge branch 'maxima-bin-remove' into pytest_replace_doctest
tobiasdiez Sep 28, 2025
46ffe33
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Sep 28, 2025
5a7aea3
Merge remote-tracking branch 'upstream/develop' into pytest_replace_d…
tobiasdiez Oct 28, 2025
09db99a
Merge branch 'develop' into pytest_replace_doctest
tobiasdiez Dec 18, 2025
eabca64
Merge remote-tracking branch 'origin/develop' into pytest_replace_doc…
tobiasdiez Jan 15, 2026
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 @@ -246,7 +246,6 @@ jobs:
GH_TOKEN: ${{ github.token }}

# Building

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


# Combining

- name: Download coverage artifacts
Expand Down
23 changes: 4 additions & 19 deletions .github/workflows/ci-meson.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,26 +163,11 @@ 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/' }}
if [[ "$RUNNER_OS" == "Windows" ]]; then
# Ignore errors on Windows, for now
pytest --doctest-ignore-import-errors --doctest -rfEs -s src || true
else
pytest -rfEs -s src
./sage -t ${{
matrix.tests == 'all' &&
'--all-except="$SEPARATELY_TESTED_FLAKY_FILES"' ||
'--new --long' }} -p4 --format github
pip install coverage pytest-xdist pytest-github-actions-annotate-failures
if [[ "${{ matrix.os }}" != "windows" ]]; then
pip install pytest-isolate
fi

- name: Test flaky files
# unknown issues with plural.pyx causes sporadic failure: https://github.com/sagemath/sage/issues/29528
# we rerun a few times, this step succeeds if any of the 5 runs succeed
if: runner.os != 'windows' && matrix.tests == 'all'
shell: bash -l {0}
run: |
for i in {1..5}; do
./sage -t -p4 --format github $SEPARATELY_TESTED_FLAKY_FILES && break
done
pytest --doctest-ignore-import-errors --doctest --full-trace -rfEs -s src

- name: Check that all modules can be imported
shell: bash -l {0}
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"arctanh",
"Bejger",
"bigcup",
"bitness",
"cachefunc",
"charpoly",
"classmethod",
Expand Down
85 changes: 73 additions & 12 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import inspect
import sys
import warnings
from typing import Any, Iterable, Optional, TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Iterable, Optional

import pytest
from _pytest.doctest import (
Expand Down Expand Up @@ -113,7 +113,7 @@ def _find(
)
except ImportError as exception:
if self.config.getvalue("doctest_ignore_import_errors"):
pytest.skip("unable to import module %r" % self.path)
pytest.skip(f"unable to import module {self.path}: {exception}")
else:
if isinstance(exception, ModuleNotFoundError):
# Ignore some missing features/modules for now
Expand All @@ -126,6 +126,7 @@ def _find(
):
pytest.skip(
f"unable to import module {self.path} due to missing feature {exception.name}"
f"unable to import module {self.path} due to missing feature {exception.name}"
)
raise
# Uses internal doctest module parsing mechanism.
Expand Down Expand Up @@ -179,22 +180,32 @@ def pytest_collect_file(

See `pytest documentation <https://docs.pytest.org/en/latest/reference/reference.html#std-hook-pytest_collect_file>`_.
"""
if (
file_path.parent.name == "combinat"
or file_path.parent.parent.name == "combinat"
):
if file_path.parent.name in {
"combinat",
"algebras",
"calculus",
} or file_path.parent.parent.name in {"combinat", "algebras", "calculus"}:
# Crashes CI for some reason
return IgnoreCollector.from_parent(parent)
if file_path.suffix == ".pyx":
# We don't allow pytests to be defined in Cython files.
# Normally, Cython files are filtered out already by pytest and we only
# hit this here if someone explicitly runs `pytest some_file.pyx`.
return IgnoreCollector.from_parent(parent)
if parent.config.option.doctest:
if file_path.name == "atexit.pyx" and file_path.parent.name == "cpython":
# Fails with "Fatal Python error"
return IgnoreCollector.from_parent(parent)
return SageDoctestModule.from_parent(parent, path=file_path)
else:
# We don't allow pytests to be defined in Cython files.
# Normally, Cython files are filtered out already by pytest and we only
# hit this here if someone explicitly runs `pytest some_file.pyx`.
return IgnoreCollector.from_parent(parent)
elif file_path.suffix == ".py":
if parent.config.option.doctest:
if file_path.name == "__main__.py" or file_path.name == "setup.py":
if file_path.name in {"__main__.py", "setup.py"}:
# We don't allow tests to be defined in __main__.py/setup.py files (because their import will fail).
return IgnoreCollector.from_parent(parent)
if file_path.name == "all.py":
# all.py do not contain tests and may fail when imported twice / in the wrong order
return IgnoreCollector.from_parent(parent)
if (
(
file_path.name == "postprocess.py"
Expand Down Expand Up @@ -226,12 +237,27 @@ def pytest_collect_file(
# TODO: Fix these (import fails with "RuntimeError: dictionary changed size during iteration")
return IgnoreCollector.from_parent(parent)

if (
file_path.name == "macaulay2.py"
and file_path.parent.name == "interfaces"
):
# TODO: Fix these (messes with the output stream)
return IgnoreCollector.from_parent(parent)

if (
file_path.name in ("forker.py", "reporting.py")
) and file_path.parent.name == "doctest":
# Fails with many errors due to different testing framework
return IgnoreCollector.from_parent(parent)

if (
file_path.name in ("build_options.py", "builders.py")
and file_path.parent.name == "sage_docbuild"
):
# Fails to import with Meson
# TODO: Can be removed after https://github.com/sagemath/sage/pull/39973
return IgnoreCollector.from_parent(parent)

if (
(
file_path.name == "arithgroup_generic.py"
Expand Down Expand Up @@ -303,6 +329,41 @@ def pytest_addoption(parser):
)


def pytest_ignore_collect(collection_path: Path):
if (
collection_path.parent.name == "manifolds"
or collection_path.parent.parent.name == "manifolds"
or collection_path.parent.name == "matrix"
):
# Fails with "Fatal Python error" (only when run in serial with other tests)
return True
if collection_path.parent.name == "ipython_kernel":
# Messes with the output stream
return True
if collection_path.name == "projective_curve.py":
# Hangs
return True
if collection_path.name in {
"functional.py",
"free_module_element.pyx",
"sageinspect.py",
"constructor.py",
"free_module.py",
"vector_space_morphism.py",
"vector_symbolic_dense.py",
"vector_symbolic_sparse.py",
"decorate.py",
"free_monoid_element.py",
"context_managers.py",
"complex_plot.pyx"
}:
# Fails with "Fatal Python error"
return True
if collection_path.name == "verbose.py":
# Messes with the output stream
return True


# Monkey patch exception printing to replace the full qualified name of the exception by its short name
# TODO: Remove this hack once migration to pytest is complete
import traceback
Expand Down Expand Up @@ -390,8 +451,8 @@ def tmpfile():
* https://github.com/pytest-dev/pytest/issues/13669

"""
from tempfile import NamedTemporaryFile
from os import unlink
from tempfile import NamedTemporaryFile
t = NamedTemporaryFile(delete=False)
yield t
unlink(t.name)
1 change: 1 addition & 0 deletions src/sage/categories/cartesian_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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
3 changes: 2 additions & 1 deletion src/sage/categories/morphism.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@

sage: R.<t> = ZZ[]
sage: f = R.hom([t+1])
sage: f._default_repr_()

Check failure on line 120 in src/sage/categories/morphism.pyx

View workflow job for this annotation

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

Failed example:

Failed example:: Got: 'Ring endomorphism of Univariate Polynomial Ring in t over Integer Ring\n Defn: t |--> t + 1'
'Ring endomorphism of Univariate Polynomial Ring in t over Integer Ring\n Defn: t |--> t + 1'
Ring endomorphism of Univariate Polynomial Ring in t over Integer Ring
Defn: t |--> t + 1
"""
D = self.domain()
if D is None:
Expand Down
39 changes: 10 additions & 29 deletions src/sage/doctest/forker.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@
TestResults = namedtuple('TestResults', 'failed attempted')


class SageDocTestRunner(doctest.DocTestRunner):
class SageDocTestRunner(doctest.DocTestRunner, object):

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

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP004)

src/sage/doctest/forker.py:522:48: UP004 Class `SageDocTestRunner` inherits from `object`
def __init__(self, *args, **kwds):
"""
A customized version of DocTestRunner that tracks dependencies
Expand Down Expand Up @@ -711,10 +711,10 @@
# 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 @@ -1883,17 +1883,6 @@
"""
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 @@ -2015,9 +2004,6 @@
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 @@ -2053,14 +2039,11 @@
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 @@ -2137,8 +2120,6 @@
sleep(die_timeout)
for w in workers:
w.kill()
if job_client:
job_client.release()
finally:
os._exit(0)

Expand Down Expand Up @@ -2437,7 +2418,7 @@
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 @@ -2563,7 +2544,7 @@
sage: FDS = FileDocTestSource(filename, DD)
sage: DTT = DocTestTask(FDS)
sage: DC = DocTestController(DD,[filename])
sage: ntests, results = DTT(options=DD)

Check failure on line 2547 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: ********************************************************************** File "/sage/src/sage/doctest/sources.py", line 769, in sage.doctest.sources.FileDocTestSource.create_doctests Failed example: ((bitness == '64' and ex.want == 'True \n') or (bitness == '32' and ex.want == 'False \n')) Expected: True Got: False ********************************************************************** 1 item had failures: 1 of 15 in sage.doctest.sources.FileDocTestSource.create_doctests
sage: ntests >= 300 or ntests
True
sage: sorted(results.keys())
Expand Down Expand Up @@ -2646,8 +2627,8 @@
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 @@ -2672,7 +2653,7 @@
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,5 +1,5 @@
"""
Helper for attaching tolerance information to strings
Helper for attaching metadata to doctest output.
"""

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

from typing import TypedDict

from typing_extensions import NotRequired, Unpack

Check failure on line 28 in src/sage/doctest/marked_output.py

View workflow job for this annotation

GitHub Actions / Lint

Ruff (UP035)

src/sage/doctest/marked_output.py:28:1: UP035 Import from `typing` instead: `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 @@ -43,12 +56,15 @@
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 @@ -61,7 +77,7 @@
sage: s.abs_tol
1.00000000000000e-7
"""
self.__dict__.update(kwds)
self.__dict__.update(kwargs)
return self

def __reduce__(self):
Expand All @@ -82,6 +98,11 @@
"""
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