Skip to content

Commit ab7f618

Browse files
committed
test: add gh-48 regression
1 parent 3a7fb1b commit ab7f618

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Regression reproducer for https://github.com/git-pull/gp-libs/issues/48.
2+
3+
Docutils/myst-parser do not know about ``.. doctest::`` (or related) directives
4+
until :func:`doctest_docutils.setup` registers them. The Arch Linux packaging
5+
environment executes ``pytest`` without importing our pytest plugin, so the
6+
directives are missing and ``DocutilsDocTestFinder`` returns zero doctests.
7+
8+
These tests intentionally exercise that scenario by purging the docutils
9+
directive registry before running the finder.
10+
"""
11+
12+
from __future__ import annotations
13+
14+
import doctest
15+
import textwrap
16+
import typing as t
17+
18+
import pytest
19+
from docutils.parsers.rst import directives
20+
21+
import doctest_docutils
22+
23+
if t.TYPE_CHECKING:
24+
import pathlib
25+
26+
FixtureFileDict = dict[str, str]
27+
28+
29+
class GhIssue48Fixture(t.NamedTuple):
30+
"""Test fixture for reproducing GH-48."""
31+
32+
test_id: str
33+
files: FixtureFileDict
34+
tests_found: int
35+
36+
37+
FIXTURES = [
38+
GhIssue48Fixture(
39+
test_id="reST-doctest_directive",
40+
files={
41+
"example.rst": textwrap.dedent(
42+
"""
43+
.. doctest::
44+
45+
>>> 4 + 4
46+
8
47+
""",
48+
),
49+
},
50+
tests_found=1,
51+
),
52+
GhIssue48Fixture(
53+
test_id="MyST-doctest_directive-backticks",
54+
files={
55+
"example.md": textwrap.dedent(
56+
"""
57+
```{doctest}
58+
59+
>>> 4 + 4
60+
8
61+
```
62+
""",
63+
),
64+
},
65+
tests_found=1,
66+
),
67+
GhIssue48Fixture(
68+
test_id="MyST-doctest_block-python--sphinx-inline-tabs",
69+
files={
70+
"example.md": textwrap.dedent(
71+
"""
72+
````{tab} example tab
73+
```python
74+
>>> 4 + 4
75+
8
76+
```
77+
````
78+
79+
````{tab} example second
80+
```python
81+
>>> 4 + 2
82+
6
83+
```
84+
````
85+
""",
86+
),
87+
},
88+
tests_found=2,
89+
),
90+
]
91+
92+
93+
def _directive_registry() -> dict[str, t.Any]:
94+
"""Return the docutils directive registry with typing hints."""
95+
# Access via __dict__ keeps Ruff happy and satisfies mypy with cast.
96+
return t.cast(dict[str, t.Any], directives.__dict__["_directives"])
97+
98+
99+
@pytest.fixture()
100+
def purge_doctest_directives() -> t.Iterator[None]:
101+
"""Remove doctest-related directives to mimic Arch packaging env."""
102+
saved: dict[str, t.Any] = {}
103+
target_directives = ("doctest", "testsetup", "testcleanup", "tab")
104+
registry = _directive_registry()
105+
for name in target_directives:
106+
if name in registry:
107+
saved[name] = registry[name]
108+
registry.pop(name, None)
109+
try:
110+
yield
111+
finally:
112+
registry.update(saved)
113+
114+
115+
@pytest.mark.parametrize(
116+
GhIssue48Fixture._fields,
117+
FIXTURES,
118+
ids=[fixture.test_id for fixture in FIXTURES],
119+
)
120+
@pytest.mark.parametrize("file_path_mode", ["relative", "absolute"])
121+
@pytest.mark.xfail(
122+
reason="GH-48: directives are not registered unless doctest_docutils.setup() runs",
123+
strict=False,
124+
)
125+
def test_docutils_doctest_finder_without_registered_directives(
126+
purge_doctest_directives: None,
127+
tmp_path: pathlib.Path,
128+
monkeypatch: pytest.MonkeyPatch,
129+
test_id: str,
130+
files: FixtureFileDict,
131+
tests_found: int,
132+
file_path_mode: str,
133+
) -> None:
134+
"""Recreate Arch Linux failure by skipping doctest_docutils.setup()."""
135+
tests_path = tmp_path / "tests"
136+
tests_path.mkdir()
137+
first_test_key = next(iter(files.keys()))
138+
first_test_filename: str | pathlib.Path = first_test_key
139+
140+
if file_path_mode == "absolute":
141+
first_test_filename = tests_path / first_test_filename
142+
elif file_path_mode != "relative": # pragma: no cover - defensive guard
143+
error_message = f"Unsupported file_path_mode: {file_path_mode}"
144+
raise ValueError(error_message)
145+
146+
for file_name, text in files.items():
147+
(tests_path / file_name).write_text(text, encoding="utf-8")
148+
149+
if file_path_mode == "relative":
150+
monkeypatch.chdir(tests_path)
151+
152+
finder = doctest_docutils.DocutilsDocTestFinder()
153+
text, _ = doctest._load_testfile( # type: ignore[attr-defined]
154+
str(first_test_filename),
155+
package=None,
156+
module_relative=False,
157+
encoding="utf-8",
158+
)
159+
tests = finder.find(text, str(first_test_filename))
160+
tests.sort(key=lambda doctest_case: doctest_case.name)
161+
162+
assert len(tests) == tests_found
163+
164+
for test in tests:
165+
doctest.DebugRunner(verbose=False).run(test)

0 commit comments

Comments
 (0)