-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathconftest.py
More file actions
128 lines (106 loc) · 4 KB
/
conftest.py
File metadata and controls
128 lines (106 loc) · 4 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
import os
from pathlib import Path
import re
from typing import Any
from docutils import __version_info__ as docutils_version_info
from docutils import nodes
import pytest
from sphinx import version_info
from sphinx.testing.util import SphinxTestApp
from sphinx_design._compat import findall
pytest_plugins = "sphinx.testing.fixtures"
if version_info >= (7, 2):
# see https://github.com/sphinx-doc/sphinx/pull/11526
from pathlib import Path as sphinx_path # noqa: N813
else:
from sphinx.testing.path import path as sphinx_path # type: ignore[no-redef]
class SphinxBuilder:
def __init__(self, app: SphinxTestApp, src_path: Path):
self.app = app
self._src_path = src_path
@property
def src_path(self) -> Path:
return self._src_path
@property
def out_path(self) -> Path:
return Path(self.app.outdir)
def build(self, assert_pass=True):
self.app.build()
if assert_pass:
assert self.warnings == "", self.status
return self
@property
def status(self):
return self.app._status.getvalue()
@property
def warnings(self):
return self.app._warning.getvalue()
def get_doctree(
self, docname: str, post_transforms: bool = False
) -> nodes.document:
doctree = self.app.env.get_doctree(docname)
if post_transforms:
self.app.env.apply_post_transforms(doctree, docname)
# make source path consistent for test comparisons
for node in findall(doctree)(include_self=True):
if not (hasattr(node, "get") and node.get("source")):
continue
node["source"] = Path(node["source"]).relative_to(self.src_path).as_posix()
if node["source"].endswith(".rst"):
node["source"] = node["source"][:-4]
elif node["source"].endswith(".md"):
node["source"] = node["source"][:-3]
# remove mathjax classes added by myst parser
if doctree.children and isinstance(doctree.children[0], nodes.section):
doctree.children[0]["classes"] = []
return doctree
@pytest.fixture()
def sphinx_builder(tmp_path: Path, make_app, monkeypatch):
def _create_project(
buildername: str = "html", conf_kwargs: dict[str, Any] | None = None
):
src_path = tmp_path / "srcdir"
src_path.mkdir()
conf_kwargs = conf_kwargs or {
"extensions": ["myst_parser", "sphinx_design"],
"myst_enable_extensions": ["colon_fence"],
}
content = "\n".join(
[f"{key} = {value!r}" for key, value in conf_kwargs.items()]
)
src_path.joinpath("conf.py").write_text(content, encoding="utf8")
app = make_app(
srcdir=sphinx_path(os.path.abspath(str(src_path))), # noqa: PTH100
buildername=buildername,
)
return SphinxBuilder(app, src_path)
yield _create_project
DOCUTILS_0_22_PLUS = docutils_version_info >= (0, 22)
@pytest.fixture
def normalize_doctree_xml():
"""Normalize docutils XML output for cross-version compatibility.
In docutils 0.22+, boolean attributes are serialized as "1"/"0"
instead of "True"/"False". This function normalizes to the old format
for consistent test fixtures.
"""
def _normalize(text: str) -> str:
if DOCUTILS_0_22_PLUS:
# Normalize new format (1/0) to old format (1/0)
# Only replace when it's clearly a boolean attribute value
# Pattern: attribute="1" or attribute="0"
attrs = [
"checked",
"force",
"has_title",
"internal",
"is_div",
"linenos",
"opened",
"refexplicit",
"refwarn",
"selected",
]
text = re.sub(rf' ({"|".join(attrs)})="1"', r' \1="True"', text)
text = re.sub(rf' ({"|".join(attrs)})="0"', r' \1="False"', text)
return text
return _normalize