-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathconftest.py
More file actions
156 lines (127 loc) · 6.32 KB
/
Copy pathconftest.py
File metadata and controls
156 lines (127 loc) · 6.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""Doctest configuration."""
import os
from collections.abc import Callable, Iterable, Sequence
from doctest import ELLIPSIS, NORMALIZE_WHITESPACE
from pathlib import Path
from typing import Any
from sybil import Document, Region, Sybil
from sybil.evaluators.doctest import DocTestEvaluator
from sybil.evaluators.python import PythonEvaluator
from sybil.parsers import myst
from sybil.parsers.abstract.codeblock import PythonDocTestOrCodeBlockParser
from sybil.parsers.abstract.doctest import DocTestStringParser
from optional_dependencies import OptionalDependencyEnum, auto
from optional_dependencies.utils import chain_checks, get_version, is_installed
optionflags = ELLIPSIS | NORMALIZE_WHITESPACE
# Hypothesis' 200ms per-example deadline is meaningless for tests that call into
# jax: an example that trips a fresh trace/compile, or just lands on a busy CPU
# during a full-suite run, blows past it. The deadline failure then does not
# reproduce on replay, so it surfaces as an unactionable `FlakyFailure`
# ("unreliable results: Falsified on the first call but did not on a subsequent
# one"). Turn it off globally; per-test `@settings` still override this.
#
# Imported defensively: `hypothesis` is declared only by the two `*hypothesis`
# packages and the root dev env, while each per-package CI job installs a
# minimal env (`uv sync --package <pkg>`) without it. An unconditional import
# here makes *loading this conftest* fail, which pytest reports as a usage
# error (exit code 4) before any test runs -- so it takes down every sibling
# package job, not just the hypothesis-using ones.
try:
import hypothesis
except ImportError:
pass
else:
hypothesis.settings.register_profile("unxt", deadline=None)
hypothesis.settings.load_profile("unxt")
class PlainDocTestParser:
"""Parser for plain >>> doctests in Python docstrings."""
def __init__(self, doctest_optionflags: int = 0) -> None:
self.doctest_parser = DocTestStringParser(DocTestEvaluator(doctest_optionflags))
def __call__(self, document: Document) -> Iterable[Region]:
"""Parse plain doctest prompts from Python docstring text."""
yield from self.doctest_parser(document.text, document.path)
class PyconCodeBlockParser(PythonDocTestOrCodeBlockParser):
"""Parser for MyST pycon code blocks with doctest evaluation."""
def __init__(
self,
future_imports: Sequence[str] = (),
doctest_optionflags: int = 0,
) -> None:
"""Initialize parser state."""
self.doctest_parser = DocTestStringParser(DocTestEvaluator(doctest_optionflags))
self.codeblock_parser = myst.CodeBlockParser(
language="pycon",
evaluator=PythonEvaluator(future_imports),
)
markdown_parsers: Sequence[Callable[[Document], Iterable[Region]]] = [
PyconCodeBlockParser(doctest_optionflags=optionflags),
myst.DocTestDirectiveParser(optionflags=optionflags),
myst.PythonCodeBlockParser(doctest_optionflags=optionflags),
myst.SkipParser(),
]
docs = Sybil(parsers=markdown_parsers, patterns=["*.md"])
python = Sybil(
parsers=[
myst.SkipParser(),
myst.PythonCodeBlockParser(doctest_optionflags=optionflags),
PlainDocTestParser(doctest_optionflags=optionflags),
],
patterns=["*.py"],
)
_sybil_collect_file = (docs + python).pytest()
# Sybil imports a doctest module by walking up through ``__init__.py`` dirs, so
# at a PEP 420 namespace boundary it computes a leaf-based module name instead
# of the real ``unxts.<pkg>`` one. For the packages whose leaf shadows an
# installed library (gala/xarray/hypothesis) that name collides with the real
# library and the import fails loudly. ``unxts.linalg`` has no such collision,
# so the mis-import silently succeeds instead, duplicating classes (e.g.
# ``UnitsMatrix``) between the two module trees. Their ``src`` doctests are
# instead run with pytest's ``--doctest-modules --import-mode=importlib``
# (namespace-aware) in each package's CI job, so skip them here.
# (unxts.interop.matplotlib also collides but has no ``src`` doctests, so sybil
# never imports it; it stays on the normal path.)
_DOCTEST_MODULE_SRC = (
"packages/unxts.interop.gala/src/",
"packages/unxts.interop.xarray/src/",
"packages/unxts.hypothesis/src/",
"packages/unxts.linalg/src/",
)
def pytest_collect_file(file_path: Path, parent: object) -> object:
"""Collect doctests with Sybil, except the namespace ``src`` handled above."""
if any(marker in file_path.as_posix() for marker in _DOCTEST_MODULE_SRC):
return None
return _sybil_collect_file(file_path, parent)
class OptDeps(OptionalDependencyEnum):
"""External backends and interop sub-packages for ``unxt``.
Declared locally (rather than importing the equivalent enum from ``unxt``)
so that ``conftest`` never imports ``unxt`` before ``pytest_generate_tests``
sets ``UNXT_ENABLE_RUNTIME_TYPECHECKING``.
"""
ASTROPY = auto()
UNXTS_INTEROP_MATPLOTLIB = auto()
UNXTS_LINALG = auto()
UNXTS_PARAMETRIC = auto()
#: The gala interop extra is only usable with an importable gala backend;
#: gala is skipped where it cannot build (e.g. Windows).
UNXTS_INTEROP_GALA = chain_checks(
get_version("unxts.interop.gala"), is_installed("gala")
)
collect_ignore_glob = []
if not OptDeps.ASTROPY.installed:
collect_ignore_glob.append("src/unxt/_interop/unxt_interop_astropy/*")
# The package docs are collected through the docs/packages/<name> symlinks, so
# ignore that (symlink) path, not the real package path.
if not OptDeps.UNXTS_INTEROP_GALA.installed:
collect_ignore_glob.append("docs/packages/unxts.interop.gala/*")
collect_ignore_glob.append("packages/unxts.interop.gala/docs/*")
if not OptDeps.UNXTS_INTEROP_MATPLOTLIB.installed:
collect_ignore_glob.append("docs/packages/unxts.interop.matplotlib/*")
collect_ignore_glob.append("packages/unxts.interop.matplotlib/docs/*")
if not OptDeps.UNXTS_LINALG.installed:
collect_ignore_glob.append("docs/packages/unxts.linalg/*")
collect_ignore_glob.append("packages/unxts.linalg/docs/*")
if not OptDeps.UNXTS_PARAMETRIC.installed:
collect_ignore_glob.append("docs/packages/unxts.parametric/*")
collect_ignore_glob.append("packages/unxts.parametric/docs/*")
def pytest_generate_tests(metafunc: Any) -> None:
os.environ["UNXT_ENABLE_RUNTIME_TYPECHECKING"] = "beartype.beartype"